更新
This commit is contained in:
@@ -22,7 +22,7 @@ namespace Cielonos.MainGame.Characters
|
||||
public partial class CharacterBase : SerializedMonoBehaviour
|
||||
{
|
||||
public Fraction fraction;
|
||||
public Transform flexibleCenterPoint;
|
||||
public Transform flexibleCenterPoint => bodyPartsSc.flexibleCenterPoint;
|
||||
public Transform footPoint;
|
||||
|
||||
[TitleGroup("Data & Presets")]
|
||||
@@ -53,23 +53,17 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
[TitleGroup("Navigation")]
|
||||
public HUDNavigationElement navigationElement;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
InitializeSubcontrollers();
|
||||
InitializeSubmodules();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void Reset()
|
||||
protected void Awake()
|
||||
{
|
||||
InitializeSubmodules();
|
||||
InitializeSubcontrollers();
|
||||
}
|
||||
#endif
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
vfxData.Initialize(this);
|
||||
vfxData?.Initialize(this);
|
||||
selfTimeSm?.SetUp(this);
|
||||
|
||||
if (fraction == Fraction.Enemy)
|
||||
{
|
||||
@@ -116,8 +110,12 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
public partial class CharacterBase
|
||||
{
|
||||
public AttackResult DealAttack(AttackValue attackValue, out bool isDead)
|
||||
public AttackResult GetAttacked(AttackAreaBase attackArea, out bool isDead)
|
||||
{
|
||||
eventSm.onBeforeGetAttacked.Invoke(attackArea);
|
||||
|
||||
AttackValue attackValue = attackArea.attackSm.attackValue;
|
||||
|
||||
AttackResult result = new AttackResult(attackValue.damage);
|
||||
|
||||
/*if (statusModule.IsInvincible)
|
||||
@@ -127,7 +125,7 @@ namespace Cielonos.MainGame.Characters
|
||||
return finalDamage;
|
||||
}*/
|
||||
|
||||
float finalDamage = GetDamageValueAfterResistance(attackValue);
|
||||
float finalDamage = GetFinalDamageValue(attackValue);
|
||||
|
||||
if (attributeSm.HasAttribute("Shield") && attributeSm["Shield"] > 0)
|
||||
{
|
||||
@@ -163,20 +161,17 @@ namespace Cielonos.MainGame.Characters
|
||||
return result;
|
||||
}
|
||||
|
||||
public float GetDamageValueAfterResistance(AttackValue attackValue)
|
||||
public float GetFinalDamageValue(AttackValue attackValue)
|
||||
{
|
||||
/*float reductionRate = attributeModule.currentAttributes.TryGetValue("DamageReduction", out float rate) ? rate : 0;
|
||||
return attackValue.attackType switch
|
||||
{
|
||||
AttackType.Energy => attackValue.damage * GetDamageReduction(attributeModule.GetCurrentAttribute("EnergyAttackResistance"), reductionRate),
|
||||
AttackType.Kinetics => attackValue.damage * GetDamageReduction(attributeModule.GetCurrentAttribute("KineticAttackResistance"), reductionRate),
|
||||
AttackType.Explosion => attackValue.damage * GetDamageReduction(attributeModule.GetCurrentAttribute("ExplosionAttackResistance"), reductionRate),
|
||||
AttackType.Magic => attackValue.damage * GetDamageReduction(attributeModule.GetCurrentAttribute("MagicAttackResistance"), reductionRate),
|
||||
AttackType.Elemental => attackValue.damage * GetDamageReduction(attributeModule.GetCurrentAttribute("ElementalAttackResistance"), reductionRate),
|
||||
AttackType.Pure => attackValue.damage * GetDamageReduction(0, reductionRate),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};*/
|
||||
|
||||
string dmByAttackType = attackValue.attackType.ToString() + "DamageDealtMultiplier";
|
||||
string gmByAttackType = attackValue.attackType.ToString() + "DamageGainMultiplier";
|
||||
|
||||
attackValue.damage *= attackValue.attacker.attributeSm[dmByAttackType];
|
||||
attackValue.damage *= attributeSm[gmByAttackType];
|
||||
|
||||
attackValue.damage *= attackValue.attacker.attributeSm["FinalDamageDealtMultiplier"];
|
||||
attackValue.damage *= attributeSm["FinalDamageGainMultiplier"];
|
||||
|
||||
return attackValue.damage;
|
||||
}
|
||||
}
|
||||
@@ -206,7 +201,7 @@ namespace Cielonos.MainGame.Characters
|
||||
recoveryTime = 0f;
|
||||
break;
|
||||
}
|
||||
|
||||
Debug.Log($"GetHit Disruption Animation Played: {disruptionType} - {breakthroughType}, Recovery Time: {recoveryTime}");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e65e8868feca6d4aa33663566b71707
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Sirenix.OdinInspector;
|
||||
using SLSFramework.General;
|
||||
using SLSUtilities.FunctionalAnimation;
|
||||
using UnityEngine;
|
||||
@@ -12,6 +13,8 @@ namespace Cielonos.MainGame.Characters
|
||||
public partial class AnimationSubcontrollerBase : SubcontrollerBase<CharacterBase>
|
||||
{
|
||||
public Animator animator;
|
||||
|
||||
public AnimatorStateMapper mapper;
|
||||
|
||||
public Dictionary<DisruptionType, bool> disruptionStatus;
|
||||
|
||||
@@ -34,6 +37,8 @@ namespace Cielonos.MainGame.Characters
|
||||
defaultAnimationGroup?.SetUp(this);
|
||||
disruptionStatus = new Dictionary<DisruptionType, bool>()
|
||||
{
|
||||
{ DisruptionType.ForcedAction , false},
|
||||
{ DisruptionType.ForcedExternal , false},
|
||||
{ DisruptionType.NormalExternal, false },
|
||||
{ DisruptionType.NormalAction, false },
|
||||
{ DisruptionType.Movement, false }
|
||||
@@ -77,6 +82,22 @@ namespace Cielonos.MainGame.Characters
|
||||
disruptionStatus[DisruptionType.NormalExternal] = currentIntervals.Any(interval => interval.intervalType == IntervalType.ExternalDisruption);
|
||||
disruptionStatus[DisruptionType.NormalAction] = currentIntervals.Any(interval => interval.intervalType == IntervalType.ActionDisruption);
|
||||
disruptionStatus[DisruptionType.Movement] = currentIntervals.Any(interval => interval.intervalType == IntervalType.MovementDisruption);
|
||||
if (fullBodyFuncAnimSm.currentRuntimeFuncAnim.HasIntervalType(IntervalType.ForcedActionDisruption))
|
||||
{
|
||||
disruptionStatus[DisruptionType.ForcedAction] = currentIntervals.Any(interval => interval.intervalType == IntervalType.ForcedActionDisruption);
|
||||
}
|
||||
else
|
||||
{
|
||||
disruptionStatus[DisruptionType.ForcedAction] = true;
|
||||
}
|
||||
if (fullBodyFuncAnimSm.currentRuntimeFuncAnim.HasIntervalType(IntervalType.ForcedExternalDisruption))
|
||||
{
|
||||
disruptionStatus[DisruptionType.ForcedExternal] = currentIntervals.Any(interval => interval.intervalType == IntervalType.ForcedExternalDisruption);
|
||||
}
|
||||
else
|
||||
{
|
||||
disruptionStatus[DisruptionType.ForcedExternal] = true;
|
||||
}
|
||||
isDuringRootMotion = currentIntervals.Any(interval => interval.intervalType == IntervalType.RootMotion);
|
||||
}
|
||||
}
|
||||
@@ -245,25 +266,26 @@ namespace Cielonos.MainGame.Characters
|
||||
if (direction == default || !animator.HasState(fullBodyActionIndex, Animator.StringToHash(getHitAnim)))
|
||||
{
|
||||
animator.CrossFade(getHitFrontAnim, normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
animDuration = mapper.GetClip(getHitFrontAnim).length;
|
||||
}
|
||||
else
|
||||
{
|
||||
animator.CrossFade(getHitAnim, normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
animDuration = mapper.GetClip(getHitAnim).length;
|
||||
}
|
||||
animDuration = animator.GetCurrentAnimatorStateInfo(fullBodyActionIndex).length;
|
||||
}
|
||||
else if (animator.HasState(fullBodyActionIndex, Animator.StringToHash(getHitAnimPrefix)))
|
||||
{
|
||||
getHitAnim = getHitAnimPrefix;
|
||||
animator.CrossFade(getHitAnim, normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
animDuration = animator.GetCurrentAnimatorStateInfo(fullBodyActionIndex).length;
|
||||
animDuration = mapper.GetClip(getHitAnim).length;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (animator.HasState(fullBodyActionIndex, Animator.StringToHash("GetHit")))
|
||||
{
|
||||
animator.CrossFade("GetHit", normalizedTransitionDuration, fullBodyActionIndex, 0);
|
||||
animDuration = animator.GetCurrentAnimatorStateInfo(fullBodyActionIndex).length;
|
||||
animDuration = mapper.GetClip("GetHit").length;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -0,0 +1,160 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector; // 引入 Odin 命名空间
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.Animations; // 仅编辑器下引用,用于解析Controller
|
||||
#endif
|
||||
namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
public class AnimatorStateMapper
|
||||
{
|
||||
// --- 配置区域 ---
|
||||
[Title("Bake Settings")]
|
||||
[SerializeField, LabelText("Target Animator")]
|
||||
private Animator _targetAnimatorForBake;
|
||||
|
||||
[TableList(ShowIndexLabels = true), Searchable] // Odin: 列表显示为表格,且支持搜索
|
||||
[SerializeField]
|
||||
private List<StateClipPair> _mappings = new List<StateClipPair>();
|
||||
|
||||
// --- 运行时缓存 ---
|
||||
private Dictionary<string, AnimationClip> _clipDict;
|
||||
private bool _isInitialized = false;
|
||||
|
||||
// --- 简单的数据结构 ---
|
||||
[System.Serializable]
|
||||
public struct StateClipPair
|
||||
{
|
||||
[ReadOnly] // 防止手动误改,建议通过Bake生成
|
||||
public string stateName;
|
||||
|
||||
public AnimationClip clip;
|
||||
}
|
||||
|
||||
public AnimatorStateMapper()
|
||||
{
|
||||
_mappings = new List<StateClipPair>();
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Runtime API (供宿主调用)
|
||||
// ========================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 必须在宿主的 Awake 中调用此方法构建索引
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
_clipDict = new Dictionary<string, AnimationClip>(_mappings.Count);
|
||||
foreach (var pair in _mappings)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(pair.stateName) && pair.clip != null)
|
||||
{
|
||||
// 防止重复Key报错,以后面的覆盖前面的(或者你可以选择忽略)
|
||||
_clipDict[pair.stateName] = pair.clip;
|
||||
}
|
||||
}
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
public AnimationClip GetClip(string stateName)
|
||||
{
|
||||
if (!_isInitialized) Initialize();
|
||||
|
||||
if (_clipDict.TryGetValue(stateName, out var clip))
|
||||
{
|
||||
return clip;
|
||||
}
|
||||
|
||||
Debug.LogWarning($"[AnimatorStateMapper] 未找到 State: '{stateName}' 对应的 Clip。请检查是否已 Bake 或 State 名字是否正确。");
|
||||
return null;
|
||||
}
|
||||
|
||||
public float GetClipLength(string stateName)
|
||||
{
|
||||
var clip = GetClip(stateName);
|
||||
return clip != null ? clip.length : 0f;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Editor Baking Logic (Odin Button)
|
||||
// ========================================================================
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void Bake(Animator animator)
|
||||
{
|
||||
_targetAnimatorForBake = animator;
|
||||
|
||||
var controller = _targetAnimatorForBake.runtimeAnimatorController as AnimatorController;
|
||||
|
||||
// 处理 Override Controller 的情况
|
||||
if (controller == null && _targetAnimatorForBake.runtimeAnimatorController is AnimatorOverrideController overrideCtrl)
|
||||
{
|
||||
controller = overrideCtrl.runtimeAnimatorController as AnimatorController;
|
||||
}
|
||||
|
||||
if (controller == null)
|
||||
{
|
||||
Debug.LogError("Animator 上未找到有效的 AnimatorController (或 AnimatorOverrideController) 资源!");
|
||||
return;
|
||||
}
|
||||
|
||||
_mappings.Clear();
|
||||
int count = 0;
|
||||
|
||||
// 递归遍历所有的 Layer 和 SubStateMachine
|
||||
foreach (var layer in controller.layers)
|
||||
{
|
||||
count += RecursiveProcessStateMachine(layer.stateMachine);
|
||||
}
|
||||
|
||||
Debug.Log($"<color=green>Bake 完成!共提取了 {count} 个 State-Clip 映射。</color>");
|
||||
}
|
||||
|
||||
private int RecursiveProcessStateMachine(AnimatorStateMachine stateMachine)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
// 1. 遍历当前层级的 State
|
||||
foreach (var childState in stateMachine.states)
|
||||
{
|
||||
var state = childState.state;
|
||||
var motion = state.motion;
|
||||
|
||||
// 仅处理直接引用 AnimationClip 的情况
|
||||
if (motion is AnimationClip clip)
|
||||
{
|
||||
_mappings.Add(new StateClipPair { stateName = state.name, clip = clip });
|
||||
count++;
|
||||
}
|
||||
// TODO: 如果需要支持 BlendTree,可以在这里扩展逻辑
|
||||
// else if (motion is BlendTree tree) { ... }
|
||||
}
|
||||
|
||||
// 2. 递归遍历子状态机 (Sub-State Machines)
|
||||
foreach (var childMachine in stateMachine.stateMachines)
|
||||
{
|
||||
count += RecursiveProcessStateMachine(childMachine.stateMachine);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public partial class AnimationSubcontrollerBase
|
||||
{
|
||||
[Button]
|
||||
private void MapperBake()
|
||||
{
|
||||
mapper ??= new AnimatorStateMapper();
|
||||
mapper.Bake(animator);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ea4943f9bb92174fbf6dc844cadf580
|
||||
@@ -23,6 +23,9 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
[Title("Custom Parts")]
|
||||
public Dictionary<string, Transform> customBodyParts;
|
||||
|
||||
[Title("Attachments")]
|
||||
public Dictionary<string, GameObject> attachments;
|
||||
}
|
||||
|
||||
public partial class BodyPartsSubcontroller
|
||||
@@ -48,4 +51,14 @@ namespace Cielonos.MainGame.Characters
|
||||
return customBodyParts.GetValueOrDefault(partName);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class BodyPartsSubcontroller
|
||||
{
|
||||
public AuxiliaryDrone AuxiliaryDrone => GetAttachment("AuxiliaryDrone")?.GetComponent<AuxiliaryDrone>();
|
||||
|
||||
public GameObject GetAttachment(string attachmentName)
|
||||
{
|
||||
return attachments.GetValueOrDefault(attachmentName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (feedbacks == null) return;
|
||||
|
||||
foreach (var feedbackUnit in feedbacks.Values)
|
||||
{
|
||||
float timeScaleMultiplier = owner.selfTimeSm.TimeScale;
|
||||
|
||||
@@ -56,9 +56,7 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
if (owner is Player player)
|
||||
{
|
||||
dashRotation.y = player.viewSc.lockTargetModule.isUsingLockTargetCamera
|
||||
? player.viewSc.cameraRotationSm.cinemachineEndLockYaw + angle
|
||||
: player.viewSc.cameraRotationSm.cinemachineTargetYaw + angle;
|
||||
dashRotation.y = player.viewSc.playerCamera.transform.eulerAngles.y + angle;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
public class ReactionSubcontroller : SubcontrollerBase<CharacterBase>
|
||||
public partial class ReactionSubcontroller : SubcontrollerBase<CharacterBase>
|
||||
{
|
||||
public Dictionary<BreakthroughType, bool> originalBreakthroughResistances;
|
||||
public Dictionary<BreakthroughType, bool> breakthroughResistances;
|
||||
@@ -20,8 +20,8 @@ namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
{ BreakthroughType.None, true },
|
||||
{ BreakthroughType.Weak, true },
|
||||
{ BreakthroughType.Medium, true },
|
||||
{ BreakthroughType.Heavy, true },
|
||||
{ BreakthroughType.Medium, false },
|
||||
{ BreakthroughType.Heavy, false },
|
||||
{ BreakthroughType.Disruption, false },
|
||||
{ BreakthroughType.Forced, false },
|
||||
{ BreakthroughType.Unstoppable, false },
|
||||
@@ -31,8 +31,8 @@ namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
{ BreakthroughType.None, true },
|
||||
{ BreakthroughType.Weak, true },
|
||||
{ BreakthroughType.Medium, true },
|
||||
{ BreakthroughType.Heavy, true },
|
||||
{ BreakthroughType.Medium, false },
|
||||
{ BreakthroughType.Heavy, false },
|
||||
{ BreakthroughType.Disruption, false },
|
||||
{ BreakthroughType.Forced, false },
|
||||
{ BreakthroughType.Unstoppable, false },
|
||||
@@ -61,4 +61,18 @@ namespace Cielonos.MainGame.Characters
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ReactionSubcontroller
|
||||
{
|
||||
public void InitializeResistances(EnemyRank enemyRank)
|
||||
{
|
||||
if (enemyRank == EnemyRank.Nexus || enemyRank == EnemyRank.Core)
|
||||
{
|
||||
originalBreakthroughResistances[BreakthroughType.Medium] = true;
|
||||
originalBreakthroughResistances[BreakthroughType.Heavy] = true;
|
||||
breakthroughResistances[BreakthroughType.Medium] = true;
|
||||
breakthroughResistances[BreakthroughType.Heavy] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,14 +30,26 @@ namespace Cielonos.MainGame.Characters
|
||||
public void RefreshAttribute(string attributeName)
|
||||
{
|
||||
attributeGroup.ResetAttribute(attributeName);
|
||||
|
||||
//owner.equipmentSubmodule.GetGeneralAttributeChange(attributeName, out float e_numeric, out float e_pAccumulation, out float e_pMultiplication);
|
||||
//owner.combatBuffSubmodule.GetGeneralAttributeChange(attributeName, out float cb_numeric, out float cb_pAccumulation, out float cb_pMultiplication);
|
||||
|
||||
float numeric = 0; //e_numeric + cb_numeric;
|
||||
float pAccumulation = 0; //e_pAccumulation + cb_pAccumulation;
|
||||
float pMultiplication = 1; //e_pMultiplication * cb_pMultiplication;
|
||||
float numeric = 0;
|
||||
float pAccumulation = 0;
|
||||
float pMultiplication = 1;
|
||||
|
||||
if (owner is Player player)
|
||||
{
|
||||
player.inventorySc.ApplyAttributeChange(attributeName, ref numeric, ref pAccumulation, ref pMultiplication);
|
||||
}
|
||||
|
||||
owner.buffSm.ApplyAttributeChange(attributeName, ref numeric, ref pAccumulation, ref pMultiplication);
|
||||
attributeGroup.ModifyAttribute(attributeName, numeric, pAccumulation, pMultiplication);
|
||||
}
|
||||
|
||||
public void RefreshAllAttributes()
|
||||
{
|
||||
foreach (var attributeName in attributeGroup.original.Keys)
|
||||
{
|
||||
RefreshAttribute(attributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,4 +64,28 @@ namespace Cielonos.MainGame.Characters
|
||||
return dispelledBuffs;
|
||||
}*/
|
||||
}
|
||||
|
||||
public partial class BuffSubmodule
|
||||
{
|
||||
public void ApplyAttributeChange(string attributeName, ref float numericChange, ref float percentageAccumulationChange, ref float percentageMultiplicationChange)
|
||||
{
|
||||
foreach (CharacterBuffBase buff in buffList.Where(buff => buff.attributeSubmodule != null))
|
||||
{
|
||||
if (buff.attributeSubmodule.numericChange.TryGetValue(attributeName, out float nChange))
|
||||
{
|
||||
numericChange += nChange;
|
||||
}
|
||||
|
||||
if (buff.attributeSubmodule.percentageChangeOfAccumulation.TryGetValue(attributeName, out float paChange))
|
||||
{
|
||||
percentageAccumulationChange += paChange;
|
||||
}
|
||||
|
||||
if (buff.attributeSubmodule.percentageChangeOfMultiplication.TryGetValue(attributeName, out float pmChange))
|
||||
{
|
||||
percentageMultiplicationChange *= pmChange;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,8 @@ namespace Cielonos.MainGame.Characters
|
||||
public OrderedDictionary<string, PrioritizedAction<AttackAreaBase>> onGetHit; //被击中时,参数为产生命中的攻击区域
|
||||
public OrderedDictionary<string, PrioritizedAction<AttackAreaBase>> onStartAttack; //开始攻击时,参数为产生成攻击的攻击区域
|
||||
public OrderedDictionary<string, PrioritizedAction<AttackAreaBase, AttackResult>> onFinishAttack; //完成攻击时,参数为被攻击目标和对应的攻击结果
|
||||
public OrderedDictionary<string, PrioritizedAction<AttackAreaBase, AttackResult>> onGetAttacked; //被攻击时,参数为攻击者和攻击结果
|
||||
public OrderedDictionary<string, PrioritizedAction<AttackAreaBase>> onBeforeGetAttacked; //被攻击前,参数为攻击者和攻击结果
|
||||
public OrderedDictionary<string, PrioritizedAction<AttackAreaBase, AttackResult>> onAfterGetAttacked; //被攻击后,参数为攻击者和攻击结果
|
||||
public OrderedDictionary<string, PrioritizedAction<CharacterBase, float>> onUseEnergy; //使用能量时,参数为使用能量的使用者和能量值
|
||||
public EventSubmodule(CharacterBase owner) : base(owner)
|
||||
{
|
||||
@@ -21,7 +22,8 @@ namespace Cielonos.MainGame.Characters
|
||||
onGetHit = new OrderedDictionary<string, PrioritizedAction<AttackAreaBase>>();
|
||||
onStartAttack = new OrderedDictionary<string, PrioritizedAction<AttackAreaBase>>();
|
||||
onFinishAttack = new OrderedDictionary<string, PrioritizedAction<AttackAreaBase, AttackResult>>();
|
||||
onGetAttacked = new OrderedDictionary<string, PrioritizedAction<AttackAreaBase, AttackResult>>();
|
||||
onBeforeGetAttacked = new OrderedDictionary<string, PrioritizedAction<AttackAreaBase>>();
|
||||
onAfterGetAttacked = new OrderedDictionary<string, PrioritizedAction<AttackAreaBase, AttackResult>>();
|
||||
onUseEnergy = new OrderedDictionary<string, PrioritizedAction<CharacterBase, float>>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,19 +70,21 @@ namespace Cielonos.MainGame
|
||||
|
||||
public bool CheckPlayability(DisruptionType disruptionType = DisruptionType.NormalAction)
|
||||
{
|
||||
if (currentRuntimeFuncAnim == null || disruptionType == DisruptionType.Death)
|
||||
if (character.statusSm.isDead)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentRuntimeFuncAnim == null || disruptionType == DisruptionType.Death || disruptionType == DisruptionType.Must)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (disruptionType == DisruptionType.ForcedAction || disruptionType == DisruptionType.ForcedExternal)
|
||||
|
||||
if (disruptionType == DisruptionType.ForcedAction)
|
||||
{
|
||||
if (character.statusSm.isDead || character.statusSm.HasStatus(StatusType.Incapacitation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return animationSc.disruptionStatus[DisruptionType.ForcedAction] ||
|
||||
animationSc.disruptionStatus[DisruptionType.NormalAction] ||
|
||||
animationSc.disruptionStatus[DisruptionType.Movement];
|
||||
}
|
||||
|
||||
if (disruptionType == DisruptionType.NormalAction)
|
||||
@@ -103,7 +105,7 @@ namespace Cielonos.MainGame
|
||||
return disruptionType switch
|
||||
{
|
||||
DisruptionType.None => false,
|
||||
DisruptionType.Must or DisruptionType.Death or DisruptionType.ForcedAction or DisruptionType.ForcedExternal => true,
|
||||
DisruptionType.Must or DisruptionType.Death => true,
|
||||
_ => animationSc.disruptionStatus[disruptionType]
|
||||
};
|
||||
}
|
||||
@@ -130,10 +132,6 @@ namespace Cielonos.MainGame
|
||||
|
||||
if (!CheckPlayability(newRtFuncAnim.funcAnimData.animInfo.disruptionType))
|
||||
{
|
||||
Debug.LogWarning($"[FunctionalAnimationSubmodule] Cannot play animation '{animationName}' due to playability check failure." +
|
||||
$"CurrentAnimation={currentRuntimeFuncAnim?.funcAnimData.animInfo.animationName ?? "None"} " +
|
||||
$"NewRtFuncAnim: disruptionType={newRtFuncAnim.funcAnimData.animInfo.disruptionType}" +
|
||||
$"IsDuringDisruption={animationSc.disruptionStatus[newRtFuncAnim.funcAnimData.animInfo.disruptionType]}");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -144,7 +142,8 @@ namespace Cielonos.MainGame
|
||||
currentRuntimeFuncAnim.InvokeEndEvents();
|
||||
ResetPlayerPreinput();
|
||||
}
|
||||
|
||||
|
||||
float oldClipLength = currentRuntimeFuncAnim != null ? currentClip.length : 1f;
|
||||
currentRuntimeFuncAnim = newRtFuncAnim;
|
||||
currentRuntimeFuncAnim.ClearRuntimeEvents();
|
||||
runtimeStartEvents?.ForEach(payload => currentRuntimeFuncAnim.AddStartEvent(payload));
|
||||
@@ -157,7 +156,7 @@ namespace Cielonos.MainGame
|
||||
|
||||
currentRuntimeFuncAnim.currentPlayTime = animInfo.overrideStartFrame / currentClip.frameRate;
|
||||
float normalizedTimeOffset = currentPlayTime / currentClip.length;
|
||||
float normalizedTransitionDuration = isNormalizedTransition ? transitionDuration : transitionDuration / currentClip.length;
|
||||
float normalizedTransitionDuration = isNormalizedTransition ? transitionDuration : transitionDuration / oldClipLength;
|
||||
animator.CrossFade(animInfo.stateName, normalizedTransitionDuration, animator.GetLayerIndex(animatorLayerName), normalizedTimeOffset);
|
||||
float actionSpeed = animInfo.overridePlaySpeed * currentPlaySpeedMultiplier;
|
||||
animator.SetFloat(ActionSpeed, actionSpeed);
|
||||
@@ -170,6 +169,16 @@ namespace Cielonos.MainGame
|
||||
currentRuntimeFuncAnim.SetUpdateUntilEventsStatus();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止指定名称的动画,默认为强制停止
|
||||
/// </summary>
|
||||
public bool Stop(string animationName, DisruptionType disruptionType = DisruptionType.Must, float transitionDuration = 0.1f)
|
||||
{
|
||||
if (currentRuntimeFuncAnim == null) return true;
|
||||
if (currentRuntimeFuncAnim.funcAnimData.animInfo.animationName != animationName) return false;
|
||||
return Stop(disruptionType, transitionDuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止当前动画
|
||||
@@ -185,7 +194,7 @@ namespace Cielonos.MainGame
|
||||
|
||||
if(currentRuntimeFuncAnim == null) return true;
|
||||
|
||||
if (disruptionType != DisruptionType.Death)
|
||||
if (disruptionType != DisruptionType.Death && disruptionType != DisruptionType.Must)
|
||||
{
|
||||
if (!CheckDisruption(disruptionType))
|
||||
{
|
||||
@@ -201,15 +210,13 @@ namespace Cielonos.MainGame
|
||||
ResetPlayerPreinput();
|
||||
}
|
||||
|
||||
float normalizedTransitionDuration = transitionDuration / currentClip.length;
|
||||
float oldClipLength = currentRuntimeFuncAnim != null ? currentClip.length : 1f;
|
||||
float normalizedTransitionDuration = transitionDuration / oldClipLength;
|
||||
animator.CrossFade("Empty", normalizedTransitionDuration, animator.GetLayerIndex(animatorLayerName));
|
||||
animationSc.disruptionStatus[DisruptionType.NormalExternal] = false;
|
||||
animationSc.disruptionStatus[DisruptionType.NormalAction] = false;
|
||||
animationSc.disruptionStatus[DisruptionType.Movement] = false;
|
||||
currentRuntimeFuncAnim = null;
|
||||
|
||||
Debug.Log("[FunctionalAnimationSubmodule] Animation stopped successfully.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -224,6 +231,8 @@ namespace Cielonos.MainGame
|
||||
|
||||
if (currentPlayTime >= currentClip.length)
|
||||
{
|
||||
UpdateEvents();
|
||||
|
||||
if (currentClip.isLooping)
|
||||
{
|
||||
currentRuntimeFuncAnim.currentPlayTime %= currentClip.length;
|
||||
|
||||
@@ -87,8 +87,10 @@ namespace Cielonos.MainGame.Characters
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void SetUp(CharacterBase entity)
|
||||
{
|
||||
if (entity.animationSc != null)
|
||||
{
|
||||
timeScaleObservable.Subscribe(timeScale =>
|
||||
|
||||
Reference in New Issue
Block a user