Files
Cielonos/Assets/Scripts/MainGame/Characters/Player/Player.cs
SoulliesOfficial 39b43680a9 爆更
2026-07-18 03:16:20 -04:00

344 lines
14 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.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<PlayerInputSubcontroller>();
operationSc ??= GetComponent<PlayerOperationSubcontroller>();
interactionSc ??= GetComponent<PlayerInteractionSubcontroller>();
viewSc ??= GetComponent<PlayerViewSubcontroller>();
inventorySc ??= GetComponent<PlayerInventorySubcontroller>();
}
protected override void InitializeSubmodules()
{
//globalAttributeSm = new AttributeSubmodule(this, globalAttributeData);
base.InitializeSubmodules();
base.eventSm = new PlayerEventSubmodule(this);
eventSm.onGetHit.InsertByPriority("Feedback_GetHit", new PrioritizedAction<AttackAreaBase>(Feedback_GetHit));
eventSm.onAfterGetAttacked.InsertByPriority("Feedback_GetAttacked", new PrioritizedAction<AttackAreaBase, Attack.Result>(Feedback_GetAttacked));
eventSm.onNormalDodgeSuccess.InsertByPriority("Feedback_NormalDodge", new PrioritizedAction<AttackAreaBase, DodgeSource>(Feedback_NormalDodge));
eventSm.onPerfectDodgeSuccess.InsertByPriority("Feedback_PerfectDodge", new PrioritizedAction<AttackAreaBase, DodgeSource>(Feedback_PerfectDodge));
}
/// <summary>
/// 注册 Player 特有的属性变更回调:
/// Health/MaxHealth → HealthBar UI 更新Energy/MaxEnergy → EnergyBar UI 更新。
/// 基类已注册 Health→onHealthChanged、Energy→onEnergyChanged 的自动触发。
/// </summary>
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();
}
}
/// <summary>
/// 属性变更 → UI 更新回调
/// </summary>
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);
}
}
}
}
}