Files
Cielonos/Assets/Scripts/MainGame/Characters/Player/Player.cs

344 lines
14 KiB
C#
Raw Normal View History

2025-12-08 05:27:53 -05:00
using System.Collections.Generic;
2026-06-30 01:48:58 -04:00
using Cielonos.MainGame;
2026-03-20 12:07:44 -04:00
using Cielonos.MainGame.Buffs.Character;
2026-06-27 12:52:03 -04:00
using Cielonos.MainGame.Effects.Feedback;
2026-02-13 09:22:11 -05:00
using Cielonos.MainGame.UI;
2025-11-25 08:19:33 -05:00
using Sirenix.OdinInspector;
2026-06-27 12:52:03 -04:00
using SLSUtilities.Feedback;
2026-02-13 09:22:11 -05:00
using SLSUtilities.General;
2025-11-25 08:19:33 -05:00
using UnityEngine;
using UnityEngine.Serialization;
namespace Cielonos.MainGame.Characters
{
public partial class Player : CharacterBase
{
2026-03-20 12:07:44 -04:00
[TitleGroup("Data")]
public AttributeData globalAttributeData;
[TitleGroup("Data")]
public PlayerViewPreset baseViewPreset;
[TitleGroup("Submodules")]
public AttributeSubmodule globalAttributeSm;
2026-01-03 18:19:39 -05:00
public PlayerEventSubmodule eventSm => base.eventSm as PlayerEventSubmodule;
2025-11-25 08:19:33 -05:00
[Required]
2026-03-20 12:07:44 -04:00
[TitleGroup("Subcontrollers")]
2025-11-25 08:19:33 -05:00
public PlayerInputSubcontroller inputSc;
[Required]
2026-03-20 12:07:44 -04:00
[TitleGroup("Subcontrollers")]
2025-11-25 08:19:33 -05:00
public PlayerOperationSubcontroller operationSc;
[Required]
2026-03-20 12:07:44 -04:00
[TitleGroup("Subcontrollers")]
2026-02-13 09:22:11 -05:00
public PlayerInteractionSubcontroller interactionSc;
[Required]
2026-03-20 12:07:44 -04:00
[TitleGroup("Subcontrollers")]
2025-11-25 08:19:33 -05:00
public PlayerViewSubcontroller viewSc;
[Required]
2026-03-20 12:07:44 -04:00
[TitleGroup("Subcontrollers")]
2025-11-25 08:19:33 -05:00
public PlayerLandMovementSubcontroller landMovementSc => base.movementSc as PlayerLandMovementSubcontroller;
[Required]
2026-03-20 12:07:44 -04:00
[TitleGroup("Subcontrollers")]
2025-11-25 08:19:33 -05:00
public PlayerInventorySubcontroller inventorySc;
[HideInEditorMode]
public new PlayerAnimationSubcontroller animationSc => base.animationSc as PlayerAnimationSubcontroller;
}
public partial class Player
{
2026-07-18 03:16:20 -04:00
protected override void CollectSubcontrollers()
{
base.CollectSubcontrollers();
inputSc ??= GetComponent<PlayerInputSubcontroller>();
operationSc ??= GetComponent<PlayerOperationSubcontroller>();
interactionSc ??= GetComponent<PlayerInteractionSubcontroller>();
viewSc ??= GetComponent<PlayerViewSubcontroller>();
inventorySc ??= GetComponent<PlayerInventorySubcontroller>();
}
2025-12-08 05:27:53 -05:00
protected override void InitializeSubmodules()
{
2026-05-23 08:27:50 -04:00
//globalAttributeSm = new AttributeSubmodule(this, globalAttributeData);
2025-12-08 05:27:53 -05:00
base.InitializeSubmodules();
2026-01-03 18:19:39 -05:00
base.eventSm = new PlayerEventSubmodule(this);
2025-12-08 05:27:53 -05:00
eventSm.onGetHit.InsertByPriority("Feedback_GetHit", new PrioritizedAction<AttackAreaBase>(Feedback_GetHit));
2026-05-23 08:27:50 -04:00
eventSm.onAfterGetAttacked.InsertByPriority("Feedback_GetAttacked", new PrioritizedAction<AttackAreaBase, Attack.Result>(Feedback_GetAttacked));
2026-04-18 13:57:19 -04:00
eventSm.onNormalDodgeSuccess.InsertByPriority("Feedback_NormalDodge", new PrioritizedAction<AttackAreaBase, DodgeSource>(Feedback_NormalDodge));
eventSm.onPerfectDodgeSuccess.InsertByPriority("Feedback_PerfectDodge", new PrioritizedAction<AttackAreaBase, DodgeSource>(Feedback_PerfectDodge));
2025-12-08 05:27:53 -05:00
}
2026-05-23 08:27:50 -04:00
/// <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);
}
2025-11-25 08:19:33 -05:00
protected override void InitializeSubcontrollers()
{
base.InitializeSubcontrollers();
inputSc.Initialize();
operationSc.Initialize();
2026-02-13 09:22:11 -05:00
interactionSc.Initialize();
2025-11-25 08:19:33 -05:00
viewSc.Initialize();
inventorySc.Initialize();
2026-01-03 18:19:39 -05:00
}
protected override void Start()
{
base.Start();
2026-07-18 03:16:20 -04:00
inventorySc.GrantInitialEquipments();
2026-06-02 12:55:39 -04:00
if(inventorySc.equipmentSm.preparedMainWeapons.Count > 0)
{
inventorySc.equipmentSm.EquipMainWeapon(inventorySc.equipmentSm.preparedMainWeapons[0]);
}
2026-01-03 18:19:39 -05:00
2026-05-10 11:47:55 -04:00
for (int i = 0; i < inventorySc.backpackSm.supportEquipments.Count; i++)
2026-01-03 18:19:39 -05:00
{
2026-05-10 11:47:55 -04:00
inventorySc.equipmentSm.EquipSupportEquipment(inventorySc.backpackSm.supportEquipments[i], i);
2026-01-03 18:19:39 -05:00
}
}
protected override void Update()
{
base.Update();
Regeneration();
2025-12-08 05:27:53 -05:00
}
}
2026-05-23 08:27:50 -04:00
/// <summary>
/// 属性变更 → UI 更新回调
/// </summary>
2025-12-08 05:27:53 -05:00
public partial class Player
{
2026-05-23 08:27:50 -04:00
private void OnHealthValueChanged(float oldValue, float newValue)
2025-12-08 05:27:53 -05:00
{
2026-05-10 11:47:55 -04:00
PlayerCanvas.PlayerInfoUIArea.UpdateHealth();
2026-02-13 09:22:11 -05:00
2026-05-23 08:27:50 -04:00
if (newValue < oldValue)
2026-02-13 09:22:11 -05:00
{
2026-05-10 11:47:55 -04:00
PlayerCanvas.PlayerInfoUIArea.healthBar.GetHurtGlowBlink();
2026-02-13 09:22:11 -05:00
}
2025-12-08 05:27:53 -05:00
}
2026-05-23 08:27:50 -04:00
private void OnMaxHealthValueChanged(float oldValue, float newValue)
{
2026-06-27 12:52:03 -04:00
float diff = newValue - oldValue;
if (diff > 0)
{
attributeSm[CharacterAttribute.Health] += diff;
}
else
{
attributeSm[CharacterAttribute.Health] = Mathf.Min(attributeSm[CharacterAttribute.Health], newValue);
}
2026-05-23 08:27:50 -04:00
PlayerCanvas.PlayerInfoUIArea.UpdateHealth();
}
private void OnEnergyValueChanged(float oldValue, float newValue)
{
PlayerCanvas.PlayerInfoUIArea.UpdateEnergy();
}
2025-12-08 05:27:53 -05:00
2026-05-23 08:27:50 -04:00
private void OnMaxEnergyValueChanged(float oldValue, float newValue)
2025-12-08 05:27:53 -05:00
{
2026-06-27 12:52:03 -04:00
float diff = newValue - oldValue;
if (diff > 0)
{
attributeSm[CharacterAttribute.Energy] += diff;
}
else
{
attributeSm[CharacterAttribute.Energy] = Mathf.Min(attributeSm[CharacterAttribute.Energy], newValue);
}
2026-05-10 11:47:55 -04:00
PlayerCanvas.PlayerInfoUIArea.UpdateEnergy();
2025-12-08 05:27:53 -05:00
}
2026-05-23 08:27:50 -04:00
private void OnShieldValueChanged(float oldValue, float newValue)
{
// 护盾值变更时刷新 HealthBar护盾通常与血条关联显示
PlayerCanvas.PlayerInfoUIArea.UpdateHealth();
}
2025-12-08 05:27:53 -05:00
2026-05-23 08:27:50 -04:00
private void OnMaxShieldValueChanged(float oldValue, float newValue)
{
PlayerCanvas.PlayerInfoUIArea.UpdateHealth();
}
}
public partial class Player
{
2025-12-08 05:27:53 -05:00
private void Feedback_GetHit(AttackAreaBase attackArea)
{
2026-07-18 03:16:20 -04:00
if (attackArea == null || attackArea.attackSm?.attackValue == null) return;
2026-05-23 08:27:50 -04:00
Breakthrough.Type breakthroughType = attackArea.attackSm.attackValue.breakthroughType;
if(breakthroughType == Breakthrough.Type.None) return;
2026-07-18 03:16:20 -04:00
if (breakthroughType is Breakthrough.Type.Disruption or Breakthrough.Type.Forced) breakthroughType = Breakthrough.Type.Heavy;
2025-12-08 05:27:53 -05:00
string feedbackName = "GetHit" + breakthroughType.ToString();
2026-07-18 03:16:20 -04:00
if (!feedbackSc.TryGetFeedbackData(feedbackName, out FeedbackData feedbackData)) return;
feedbackSc.PlayFeedback(feedbackData, runtimeFeedback =>
{
ApplyGetHitFeedbackDirection(runtimeFeedback, attackArea);
});
2025-12-08 05:27:53 -05:00
}
2026-05-23 08:27:50 -04:00
private void Feedback_GetAttacked(AttackAreaBase attackArea, Attack.Result attackResult)
2025-12-08 05:27:53 -05:00
{
2026-07-18 03:16:20 -04:00
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;
2025-12-08 05:27:53 -05:00
float ratio = attackResult.finalDamage / (attributeSm["MaximumHealth"] * 0.2f);
float intensity = Mathf.Lerp(0.25f, 1f, ratio);
2026-07-18 03:16:20 -04:00
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;
2025-11-25 08:19:33 -05:00
}
2026-04-18 13:57:19 -04:00
private void Feedback_PerfectDodge(AttackAreaBase attackArea, DodgeSource dodgeSource)
{
2026-07-18 03:16:20 -04:00
feedbackSc.TryPlayFeedback("PerfectDodge");
2026-04-18 13:57:19 -04:00
}
private void Feedback_NormalDodge(AttackAreaBase attackArea, DodgeSource dodgeSource)
{
2026-07-18 03:16:20 -04:00
feedbackSc.TryPlayFeedback("NormalDodge");
2026-04-18 13:57:19 -04:00
}
2026-06-27 12:52:03 -04:00
public override void RecoverEnergy(float energyAmount, bool spawnText = true)
{
2026-06-30 01:48:58 -04:00
if (Mathf.Abs(energyAmount) < 1e-5f) return;
2026-06-27 12:52:03 -04:00
2026-06-30 01:48:58 -04:00
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]);
}
2026-06-27 12:52:03 -04:00
if (spawnText)
{
var textPrefab = MainGameBaseCollection.Instance.EnergyText();
if (textPrefab != null)
{
textPrefab.Spawn(CenterPosition, energyAmount, CenterPoint);
}
}
}
2025-11-25 08:19:33 -05:00
}
2026-05-23 08:27:50 -04:00
}