using System.Collections.Generic; using Cielonos.MainGame; using Cielonos.MainGame.Buffs.Character; using Cielonos.MainGame.Effects.Feedback; using Cielonos.MainGame.UI; using Sirenix.OdinInspector; using SLSUtilities.Feedback; using SLSUtilities.General; using UnityEngine; using UnityEngine.Serialization; namespace Cielonos.MainGame.Characters { public partial class Player : CharacterBase { [TitleGroup("Data")] public AttributeData globalAttributeData; [TitleGroup("Data")] public PlayerViewPreset baseViewPreset; [TitleGroup("Submodules")] public AttributeSubmodule globalAttributeSm; public PlayerEventSubmodule eventSm => base.eventSm as PlayerEventSubmodule; [Required] [TitleGroup("Subcontrollers")] public PlayerInputSubcontroller inputSc; [Required] [TitleGroup("Subcontrollers")] public PlayerOperationSubcontroller operationSc; [Required] [TitleGroup("Subcontrollers")] public PlayerInteractionSubcontroller interactionSc; [Required] [TitleGroup("Subcontrollers")] public PlayerViewSubcontroller viewSc; [Required] [TitleGroup("Subcontrollers")] public PlayerLandMovementSubcontroller landMovementSc => base.movementSc as PlayerLandMovementSubcontroller; [Required] [TitleGroup("Subcontrollers")] public PlayerInventorySubcontroller inventorySc; [HideInEditorMode] public new PlayerAnimationSubcontroller animationSc => base.animationSc as PlayerAnimationSubcontroller; } public partial class Player { protected override void CollectSubcontrollers() { base.CollectSubcontrollers(); inputSc ??= GetComponent(); operationSc ??= GetComponent(); interactionSc ??= GetComponent(); viewSc ??= GetComponent(); inventorySc ??= GetComponent(); } protected override void InitializeSubmodules() { //globalAttributeSm = new AttributeSubmodule(this, globalAttributeData); base.InitializeSubmodules(); base.eventSm = new PlayerEventSubmodule(this); eventSm.onGetHit.InsertByPriority("Feedback_GetHit", new PrioritizedAction(Feedback_GetHit)); eventSm.onAfterGetAttacked.InsertByPriority("Feedback_GetAttacked", new PrioritizedAction(Feedback_GetAttacked)); eventSm.onNormalDodgeSuccess.InsertByPriority("Feedback_NormalDodge", new PrioritizedAction(Feedback_NormalDodge)); eventSm.onPerfectDodgeSuccess.InsertByPriority("Feedback_PerfectDodge", new PrioritizedAction(Feedback_PerfectDodge)); } /// /// 注册 Player 特有的属性变更回调: /// Health/MaxHealth → HealthBar UI 更新;Energy/MaxEnergy → EnergyBar UI 更新。 /// 基类已注册 Health→onHealthChanged、Energy→onEnergyChanged 的自动触发。 /// protected override void RegisterAttributeCallbacks() { base.RegisterAttributeCallbacks(); attributeSm.RegisterValueChangedCallback(CharacterAttribute.Health, OnHealthValueChanged); attributeSm.RegisterValueChangedCallback(CharacterAttribute.MaximumHealth, OnMaxHealthValueChanged); attributeSm.RegisterValueChangedCallback(CharacterAttribute.Energy, OnEnergyValueChanged); attributeSm.RegisterValueChangedCallback(CharacterAttribute.MaximumEnergy, OnMaxEnergyValueChanged); attributeSm.RegisterValueChangedCallback(CharacterAttribute.Shield, OnShieldValueChanged); } protected override void InitializeSubcontrollers() { base.InitializeSubcontrollers(); inputSc.Initialize(); operationSc.Initialize(); interactionSc.Initialize(); viewSc.Initialize(); inventorySc.Initialize(); } protected override void Start() { base.Start(); inventorySc.GrantInitialEquipments(); if(inventorySc.equipmentSm.preparedMainWeapons.Count > 0) { inventorySc.equipmentSm.EquipMainWeapon(inventorySc.equipmentSm.preparedMainWeapons[0]); } for (int i = 0; i < inventorySc.backpackSm.supportEquipments.Count; i++) { inventorySc.equipmentSm.EquipSupportEquipment(inventorySc.backpackSm.supportEquipments[i], i); } } protected override void Update() { base.Update(); Regeneration(); } } /// /// 属性变更 → UI 更新回调 /// public partial class Player { private void OnHealthValueChanged(float oldValue, float newValue) { PlayerCanvas.PlayerInfoUIArea.UpdateHealth(); if (newValue < oldValue) { PlayerCanvas.PlayerInfoUIArea.healthBar.GetHurtGlowBlink(); } } private void OnMaxHealthValueChanged(float oldValue, float newValue) { float diff = newValue - oldValue; if (diff > 0) { attributeSm[CharacterAttribute.Health] += diff; } else { attributeSm[CharacterAttribute.Health] = Mathf.Min(attributeSm[CharacterAttribute.Health], newValue); } PlayerCanvas.PlayerInfoUIArea.UpdateHealth(); } private void OnEnergyValueChanged(float oldValue, float newValue) { PlayerCanvas.PlayerInfoUIArea.UpdateEnergy(); } private void OnMaxEnergyValueChanged(float oldValue, float newValue) { float diff = newValue - oldValue; if (diff > 0) { attributeSm[CharacterAttribute.Energy] += diff; } else { attributeSm[CharacterAttribute.Energy] = Mathf.Min(attributeSm[CharacterAttribute.Energy], newValue); } PlayerCanvas.PlayerInfoUIArea.UpdateEnergy(); } private void OnShieldValueChanged(float oldValue, float newValue) { // 护盾值变更时刷新 HealthBar(护盾通常与血条关联显示) PlayerCanvas.PlayerInfoUIArea.UpdateHealth(); } private void OnMaxShieldValueChanged(float oldValue, float newValue) { PlayerCanvas.PlayerInfoUIArea.UpdateHealth(); } } public partial class Player { private void Feedback_GetHit(AttackAreaBase attackArea) { if (attackArea == null || attackArea.attackSm?.attackValue == null) return; Breakthrough.Type breakthroughType = attackArea.attackSm.attackValue.breakthroughType; if(breakthroughType == Breakthrough.Type.None) return; if (breakthroughType is Breakthrough.Type.Disruption or Breakthrough.Type.Forced) breakthroughType = Breakthrough.Type.Heavy; string feedbackName = "GetHit" + breakthroughType.ToString(); if (!feedbackSc.TryGetFeedbackData(feedbackName, out FeedbackData feedbackData)) return; feedbackSc.PlayFeedback(feedbackData, runtimeFeedback => { ApplyGetHitFeedbackDirection(runtimeFeedback, attackArea); }); } private void Feedback_GetAttacked(AttackAreaBase attackArea, Attack.Result attackResult) { if (attackResult == null || feedbackSc == null) return; FeedbackData feedbackData = ResolveGetAttackedFeedbackData(); if (feedbackData == null) return; feedbackSc.PlayFeedback(feedbackData, runtimeFeedback => { ApplyGetAttackedFeedbackIntensity(runtimeFeedback, attackResult); }); } private FeedbackData ResolveGetAttackedFeedbackData() { feedbackSc.TryGetFeedbackData("GetAttacked", out FeedbackData defaultFeedback); return defaultFeedback; } private void ApplyGetAttackedFeedbackIntensity(RuntimeFeedback runtimeFeedback, Attack.Result attackResult) { RGBSplitGlitchAction glitchAction = FindGetAttackedGlitchAction(runtimeFeedback); if (glitchAction == null) return; float ratio = attackResult.finalDamage / (attributeSm["MaximumHealth"] * 0.2f); float intensity = Mathf.Lerp(0.25f, 1f, ratio); glitchAction.intensityCurve.remapMax = intensity; } private static RGBSplitGlitchAction FindGetAttackedGlitchAction(RuntimeFeedback runtimeFeedback) { if (runtimeFeedback?.tracks == null) return null; for (int trackIndex = 0; trackIndex < runtimeFeedback.tracks.Count; trackIndex++) { FeedbackTrack track = runtimeFeedback.tracks[trackIndex]; if (track == null || track.trackName != "Postprocessing" || track.clips == null) continue; for (int clipIndex = 0; clipIndex < track.clips.Count; clipIndex++) { FeedbackClip clip = track.clips[clipIndex]; if (clip?.action is RGBSplitGlitchAction glitchAction) { return glitchAction; } } } return null; } private void ApplyGetHitFeedbackDirection(RuntimeFeedback runtimeFeedback, AttackAreaBase attackArea) { CameraPositionShakeAction positionShakeAction = FindGetHitPositionShakeAction(runtimeFeedback); if (positionShakeAction == null) return; float baseStrength = attackArea.attackSm.attackValue.breakthroughType switch { Breakthrough.Type.Weak => 0.2f, Breakthrough.Type.Medium => 0.4f, Breakthrough.Type.Heavy or Breakthrough.Type.Disruption or Breakthrough.Type.Forced => 0.8f, _ => 0f }; if (baseStrength <= 0f) return; Vector3 cameraLocalDirection = FeedbackVectorUtility.GetCameraLocalAttackDirection(attackArea, this, viewSc.playerCamera); positionShakeAction.amplitude = FeedbackVectorUtility.DirectionToAmplitude(cameraLocalDirection, baseStrength); Debug.Log($"[Player] Applied GetHit feedback direction: {positionShakeAction.amplitude}"); } private static CameraPositionShakeAction FindGetHitPositionShakeAction(RuntimeFeedback runtimeFeedback) { if (runtimeFeedback?.tracks == null) return null; for (int trackIndex = 0; trackIndex < runtimeFeedback.tracks.Count; trackIndex++) { FeedbackTrack track = runtimeFeedback.tracks[trackIndex]; if (track == null || track.trackName != "Camera" || track.clips == null) continue; for (int clipIndex = 0; clipIndex < track.clips.Count; clipIndex++) { FeedbackClip clip = track.clips[clipIndex]; if (clip?.action is CameraPositionShakeAction positionShakeAction) { return positionShakeAction; } } } return null; } private void Feedback_PerfectDodge(AttackAreaBase attackArea, DodgeSource dodgeSource) { feedbackSc.TryPlayFeedback("PerfectDodge"); } private void Feedback_NormalDodge(AttackAreaBase attackArea, DodgeSource dodgeSource) { feedbackSc.TryPlayFeedback("NormalDodge"); } public override void RecoverEnergy(float energyAmount, bool spawnText = true) { if (Mathf.Abs(energyAmount) < 1e-5f) return; if (energyAmount > 0) { float current = attributeSm[CharacterAttribute.Energy]; float max = attributeSm[CharacterAttribute.MaximumEnergy]; float availableSpace = max - current; if (energyAmount > availableSpace) { attributeSm[CharacterAttribute.Energy] = max; float conversionRate = attributeSm.Has(CharacterAttribute.OverloadConversionRate) ? attributeSm[CharacterAttribute.OverloadConversionRate] : 1f; float overflowEnergy = (energyAmount - availableSpace) * conversionRate; DistributeOverloadEnergy(overflowEnergy); } else { attributeSm[CharacterAttribute.Energy] += energyAmount; } } else { attributeSm[CharacterAttribute.Energy] += energyAmount; attributeSm[CharacterAttribute.Energy] = Mathf.Max(0, attributeSm[CharacterAttribute.Energy]); } if (spawnText) { var textPrefab = MainGameBaseCollection.Instance.EnergyText(); if (textPrefab != null) { textPrefab.Spawn(CenterPosition, energyAmount, CenterPoint); } } } } }