Passion & UI
This commit is contained in:
@@ -0,0 +1,378 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cielonos.MainGame.Characters;
|
||||
using Sirenix.OdinInspector;
|
||||
using SLSUtilities.General;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Cielonos.MainGame
|
||||
{
|
||||
/// <summary>
|
||||
/// 激情系统(战斗评分/战斗引擎)
|
||||
/// 管理玩家的定性与定量战斗状态。
|
||||
/// 采用逐级递进机制,每个等级具有独立的 0-100% 进度。
|
||||
/// </summary>
|
||||
public partial class PassionSystem : CombatSystemBase
|
||||
{
|
||||
// ==========================================
|
||||
// 配置与设置 / Settings & Configurations
|
||||
// ==========================================
|
||||
|
||||
[TitleGroup("衰减设置")]
|
||||
[Tooltip("每秒基础衰减率(占当前等级总进度的百分比)。")]
|
||||
[SerializeField] private float baseDecayRate = 5f;
|
||||
|
||||
// ==========================================
|
||||
// 运行时状态 / Runtime State
|
||||
// ==========================================
|
||||
|
||||
[TitleGroup("运行时状态")]
|
||||
[ReadOnly] [ShowInInspector] public int levelIndex;
|
||||
[ReadOnly] [ShowInInspector] public float percent;
|
||||
[ReadOnly] [ShowInInspector] private float _timeSinceLastAction;
|
||||
|
||||
[TitleGroup("等级配置")]
|
||||
public List<PassionLevel> passionLevels;
|
||||
|
||||
// ==========================================
|
||||
// 事件 / Events
|
||||
// ==========================================
|
||||
|
||||
/// <summary>
|
||||
/// 当激情等级(段位)发生改变时触发。参数1:旧等级索引,参数2:新等级索引。
|
||||
/// </summary>
|
||||
public event Action<int, int> OnLevelChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 当当前等级的百分比进度发生改变时触发。
|
||||
/// </summary>
|
||||
public event Action<float> OnPercentChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 当激情值增加时触发。参数1:实际增加值,参数2:是否应用了乘数(通常代表主动或技术行为加成)。
|
||||
/// </summary>
|
||||
public event Action<float, bool> OnPassionIncreased;
|
||||
|
||||
/// <summary>
|
||||
/// 当激情值减少时触发。参数1:实际减少值,参数2:是否应用了受击乘数(通常代表受击等负面反应降低)。
|
||||
/// </summary>
|
||||
public event Action<float, bool> OnPassionDecreased;
|
||||
|
||||
// ==========================================
|
||||
// 属性 / Properties
|
||||
// ==========================================
|
||||
|
||||
public int LevelIndex => levelIndex;
|
||||
public float Percent => percent;
|
||||
public float TimeSinceLastAction => _timeSinceLastAction;
|
||||
public PassionLevel.Type CurrentLevelType => passionLevels[levelIndex].levelType;
|
||||
|
||||
// ==========================================
|
||||
// 生命周期 / Lifecycle
|
||||
// ==========================================
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitializeRankInfos();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
PlayerEventSubmodule eventSm = MainGameManager.Player.eventSm;
|
||||
|
||||
eventSm.onFinishAttack.TryAdd("PassionSystem",
|
||||
new PrioritizedAction<AttackAreaBase, CharacterBase, Attack.Result>(
|
||||
(attacker, target, result) => OnAttackHit(result.value.breakthroughType)
|
||||
)
|
||||
);
|
||||
|
||||
eventSm.onPerfectBlockSuccess.TryAdd("PassionSystem",
|
||||
new PrioritizedAction<AttackAreaBase, BlockSource>(
|
||||
(attacker, source) => OnPerfectBlock()
|
||||
)
|
||||
);
|
||||
|
||||
eventSm.onPerfectDodgeSuccess.TryAdd("PassionSystem",
|
||||
new PrioritizedAction<AttackAreaBase, DodgeSource>(
|
||||
(attacker, source) => OnPerfectDodge()
|
||||
)
|
||||
);
|
||||
|
||||
eventSm.onAfterGetAttacked.TryAdd("PassionSystem",
|
||||
new PrioritizedAction<AttackAreaBase, Attack.Result>(
|
||||
(attacker, result) => OnGetHit(result.value.breakthroughType)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
var player = MainGameManager.Player;
|
||||
if (player == null || player.selfTimeSm == null) return;
|
||||
|
||||
float playerLocalDt = player.selfTimeSm.DeltaTime;
|
||||
_timeSinceLastAction += playerLocalDt;
|
||||
|
||||
// 如果无动作时间超过延迟设定,则触发自然衰减
|
||||
float keepDuration = player.attributeSm[CharacterAttribute.PassionKeepDuration];
|
||||
if (_timeSinceLastAction >= keepDuration)
|
||||
{
|
||||
float amplifier = player.attributeSm[CharacterAttribute.PassionTimeDecreaseAmplifier];
|
||||
float decayRate = baseDecayRate * passionLevels[levelIndex].timeDecreaseMultiplier * (1 + amplifier);
|
||||
|
||||
// 时间引起的自然衰减:直接传入计算时间乘数后的值,并忽略受击等其他乘数,标记 isTimeDecay 为 true
|
||||
DecreasePassion(decayRate * playerLocalDt, false, isTimeDecay: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class PassionSystem
|
||||
{
|
||||
[Serializable]
|
||||
public struct PassionLevel
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
C = 0,
|
||||
B = 1,
|
||||
A = 2,
|
||||
S = 3,
|
||||
SS = 4,
|
||||
SSS = 5
|
||||
}
|
||||
|
||||
public Type levelType;
|
||||
public Color levelColor; // 该等级的 UI 显示颜色
|
||||
public float increaseMultiplier; // 激情值获取乘数
|
||||
[FormerlySerializedAs("decayMultiplier")]
|
||||
public float timeDecreaseMultiplier; // 随时间自然衰减的乘数
|
||||
public float reactiveDecreaseMultiplier; // 受击时瞬间衰减的乘数
|
||||
|
||||
public PassionLevel(Type levelType, Color levelColor, float increaseMultiplier, float timeDecreaseMultiplier, float reactiveDecreaseMultiplier)
|
||||
{
|
||||
this.levelType = levelType;
|
||||
this.levelColor = levelColor;
|
||||
this.increaseMultiplier = increaseMultiplier;
|
||||
this.timeDecreaseMultiplier = timeDecreaseMultiplier;
|
||||
this.reactiveDecreaseMultiplier = reactiveDecreaseMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
[Button("初始化段位配置")]
|
||||
private void InitializeRankInfos()
|
||||
{
|
||||
if (passionLevels == null || passionLevels.Count == 0)
|
||||
{
|
||||
passionLevels = new List<PassionLevel>
|
||||
{
|
||||
new PassionLevel(PassionLevel.Type.C, new Color(0f, 0.8f, 1f, 1f), 1f, 1f, 1f),
|
||||
new PassionLevel(PassionLevel.Type.B, new Color(0.2f, 0.6f, 1f, 1f), 0.95f, 1f, 1.25f),
|
||||
new PassionLevel(PassionLevel.Type.A, new Color(0.4f, 0.4f, 1f, 1f), 0.9f, 1f, 1.5f),
|
||||
|
||||
new PassionLevel(PassionLevel.Type.S, new Color(0.8f, 0.2f, 0.8f, 1f), 0.85f, 1.5f, 2f),
|
||||
new PassionLevel(PassionLevel.Type.SS, new Color(0.9f, 0.2f, 0.4f, 1f), 0.8f, 1.5f, 3f),
|
||||
new PassionLevel(PassionLevel.Type.SSS, new Color(1f, 0f, 0f, 1f), 0.8f, 1.5f, 4f),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class PassionSystem
|
||||
{
|
||||
// ==========================================
|
||||
// 公共 API / Public API
|
||||
// ==========================================
|
||||
|
||||
/// <summary>
|
||||
/// 重置衰减计时器,延迟自然衰减的触发。
|
||||
/// </summary>
|
||||
public void ResetDecayTimer()
|
||||
{
|
||||
_timeSinceLastAction = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当攻击命中敌人时触发,根据打断等级(Breakthrough.Type)增加激情值。
|
||||
/// </summary>
|
||||
/// <param name="breakthroughType">攻击的打断级别</param>
|
||||
public void OnAttackHit(Breakthrough.Type breakthroughType)
|
||||
{
|
||||
float baseAmount = breakthroughType switch
|
||||
{
|
||||
Breakthrough.Type.None => 0f,
|
||||
Breakthrough.Type.Weak => 1f,
|
||||
Breakthrough.Type.Medium => 2f,
|
||||
Breakthrough.Type.Heavy => 4f,
|
||||
Breakthrough.Type.Disruption => 4f,
|
||||
Breakthrough.Type.Forced => 4f,
|
||||
_ => 0f
|
||||
};
|
||||
|
||||
Debug.Log($"[PassionSystem] OnAttackHit 触发,打断类型: {breakthroughType},激情值基础增加量: {baseAmount}%");
|
||||
IncreasePassion(baseAmount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发完美格挡时增加激情值。
|
||||
/// </summary>
|
||||
public void OnPerfectBlock()
|
||||
{
|
||||
IncreasePassion(20f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发完美闪避时增加激情值。
|
||||
/// </summary>
|
||||
public void OnPerfectDodge()
|
||||
{
|
||||
IncreasePassion(20f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当主角受到伤害时触发,根据敌人的攻击打断等级(Breakthrough.Type)降低激情值。
|
||||
/// </summary>
|
||||
/// <param name="breakthroughType">敌人攻击的打断级别</param>
|
||||
public void OnGetHit(Breakthrough.Type breakthroughType)
|
||||
{
|
||||
float baseAmount = breakthroughType switch
|
||||
{
|
||||
Breakthrough.Type.None => 0f,
|
||||
Breakthrough.Type.Weak => 1f,
|
||||
Breakthrough.Type.Medium => 5f,
|
||||
Breakthrough.Type.Heavy => 10f,
|
||||
Breakthrough.Type.Disruption => 10f,
|
||||
Breakthrough.Type.Forced => 10f,
|
||||
_ => 1f
|
||||
};
|
||||
DecreasePassion(baseAmount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加激情值。自动处理等级提升与溢出(当百分比超过 100% 时自动进入下一等级)。
|
||||
/// </summary>
|
||||
/// <param name="amount">基础增加百分比量(0-100 范围)。</param>
|
||||
/// <param name="applyMultipliers">是否应用当前等级的获取乘数。</param>
|
||||
public void IncreasePassion(float amount, bool applyMultipliers = true)
|
||||
{
|
||||
if (amount <= 0f) return;
|
||||
|
||||
// 重置衰减计时器以推迟自然衰减
|
||||
ResetDecayTimer();
|
||||
|
||||
float finalAmount = amount;
|
||||
if (applyMultipliers)
|
||||
{
|
||||
var player = MainGameManager.Player;
|
||||
float amplifier = player.attributeSm[CharacterAttribute.PassionIncreaseAmplifier];
|
||||
finalAmount *= passionLevels[levelIndex].increaseMultiplier * (1 + amplifier);
|
||||
}
|
||||
|
||||
percent += finalAmount;
|
||||
OnPassionIncreased?.Invoke(finalAmount, applyMultipliers);
|
||||
|
||||
int oldLevel = levelIndex;
|
||||
while (percent >= 100f)
|
||||
{
|
||||
if (levelIndex >= passionLevels.Count - 1)
|
||||
{
|
||||
percent = 100f; // 钳制在最大等级(SSS)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
percent -= 100f;
|
||||
levelIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldLevel != levelIndex)
|
||||
{
|
||||
OnLevelChanged?.Invoke(oldLevel, levelIndex);
|
||||
}
|
||||
|
||||
OnPercentChanged?.Invoke(percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 减少激情值。自动处理等级降低与退级(当百分比低于 0% 时自动退回上一等级)。
|
||||
/// </summary>
|
||||
/// <param name="amount">基础减少百分比量(0-100 范围)。</param>
|
||||
/// <param name="applyMultipliers">是否应用当前等级的受击衰减乘数。</param>
|
||||
/// <param name="isTimeDecay">是否为时间引起的自然衰减(如果为 true,当跌至 0% 时会短暂保留等级而非直接退级)</param>
|
||||
public void DecreasePassion(float amount, bool applyMultipliers = true, bool isTimeDecay = false)
|
||||
{
|
||||
if (amount <= 0f) return;
|
||||
|
||||
float finalAmount = amount;
|
||||
if (applyMultipliers)
|
||||
{
|
||||
var player = MainGameManager.Player;
|
||||
float amplifier = player.attributeSm[CharacterAttribute.PassionReactiveDecreaseAmplifier];
|
||||
finalAmount *= passionLevels[levelIndex].reactiveDecreaseMultiplier * (1 + amplifier);
|
||||
}
|
||||
|
||||
// 如果是时间自然衰减,且当前等级进度会从大于 0% 跌至 0% 以下:
|
||||
// 我们将其钳制在 0% 并重置衰减计时器(暂停衰减一段时间),而不是立即降低等级。
|
||||
// 注意,当percent正好等于0时,不会进入这个条件分支,于是PassionLevel会正常退级。
|
||||
// 这样设计是为了让玩家在自然衰减时有一个短暂的缓冲区,而不是直接退级,增加游戏的容错性和玩家体验。
|
||||
if (isTimeDecay)
|
||||
{
|
||||
if (percent > 0f && percent - finalAmount <= 0f)
|
||||
{
|
||||
percent = 0f;
|
||||
ResetDecayTimer();
|
||||
OnPassionDecreased?.Invoke(finalAmount, applyMultipliers);
|
||||
OnPercentChanged?.Invoke(percent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
percent -= finalAmount;
|
||||
OnPassionDecreased?.Invoke(finalAmount, applyMultipliers);
|
||||
|
||||
int oldLevel = levelIndex;
|
||||
while (percent < 0f)
|
||||
{
|
||||
if (levelIndex <= 0)
|
||||
{
|
||||
percent = 0f; // 钳制在最低等级(C)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
percent += 100f;
|
||||
levelIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldLevel != levelIndex)
|
||||
{
|
||||
OnLevelChanged?.Invoke(oldLevel, levelIndex);
|
||||
}
|
||||
|
||||
OnPercentChanged?.Invoke(percent);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public partial class PassionSystem
|
||||
{
|
||||
// ==========================================
|
||||
// 调试工具 / Debug Tools
|
||||
// ==========================================
|
||||
|
||||
[Button("Debug: 增加激情值 100%")]
|
||||
public void IncreasePassion()
|
||||
{
|
||||
IncreasePassion(100f, false);
|
||||
}
|
||||
|
||||
[Button("Debug: 减少激情值 100%")]
|
||||
public void DecreasePassion()
|
||||
{
|
||||
DecreasePassion(100f, false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user