2.5 update

This commit is contained in:
2026-09-28 11:07:17 +09:00
parent abd2da2057
commit 7978d82196
976 changed files with 111965 additions and 313 deletions
@@ -567,7 +567,7 @@ namespace BumpCombat.Progression
ShowOptions();
}
private static string GetPassiveRelicName(PassiveRelicId id)
internal static string GetPassiveRelicName(PassiveRelicId id)
{
return id switch
{
@@ -0,0 +1,56 @@
using System;
using BumpCombat.Constants;
using UnityEngine;
namespace BumpCombat.Progression
{
public sealed class ExperienceProgress
{
private float fractionalExperience;
public event Action<int, int, int> ExperienceChanged;
public event Action<int> LevelGained;
public int Level { get; private set; } = 1;
public int CurrentExperience { get; private set; }
public int RequiredExperience => RequiredExperienceForLevel(Level);
public void Reset()
{
Level = 1;
CurrentExperience = 0;
fractionalExperience = 0f;
}
public void AddEvaluated(float evaluatedAmount)
{
fractionalExperience += Mathf.Max(0f, evaluatedAmount);
int wholeExperience = Mathf.FloorToInt(
fractionalExperience + 0.0001f);
fractionalExperience = Mathf.Max(
0f,
fractionalExperience - wholeExperience);
CurrentExperience += wholeExperience;
while (CurrentExperience >= RequiredExperience)
{
CurrentExperience -= RequiredExperience;
Level++;
LevelGained?.Invoke(Level);
}
ExperienceChanged?.Invoke(
CurrentExperience,
RequiredExperience,
Level);
}
internal static int RequiredExperienceForLevel(int level)
{
LevelUpConstants tuning = GameplayConstants.Current.LevelUp;
return tuning.BaseExperienceToNextLevel
+ (Mathf.Max(1, level) - 1)
* tuning.AdditionalExperiencePerLevel;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 030f13063dd14111976e7d7cc4e95223
@@ -1,6 +1,5 @@
using BumpCombat.Core;
using System;
using BumpCombat.Constants;
using BumpCombat.Player;
using UnityEngine;
@@ -10,19 +9,26 @@ namespace BumpCombat.Progression
public sealed class ExperienceSystem : MonoBehaviour
{
private PlayerStats playerStats;
private float fractionalExperience;
private ExperienceProgress progress;
public event Action<int, int, int> OnExperienceChanged;
public event Action<int> OnLevelGained;
public event Action OnExperienceOrbPickedUp;
public int Level { get; private set; } = 1;
public int CurrentExperience { get; private set; }
public int RequiredExperience => RequiredExperienceForLevel(Level);
public int Level => progress != null ? progress.Level : 1;
public int CurrentExperience => progress != null
? progress.CurrentExperience
: 0;
public int RequiredExperience => progress != null
? progress.RequiredExperience
: RequiredExperienceForLevel(1);
private void Awake()
{
playerStats = GetComponent<PlayerStats>();
progress = new ExperienceProgress();
progress.ExperienceChanged += HandleProgressExperienceChanged;
progress.LevelGained += HandleProgressLevelGained;
}
private void Start()
@@ -48,31 +54,31 @@ namespace BumpCombat.Progression
private void AddExperienceInternal(int amount)
{
fractionalExperience += Mathf.Max(0f, playerStats.Evaluate(
float evaluatedAmount = playerStats.Evaluate(
CharacterStat.ExperienceGain,
Mathf.Max(0, amount)));
int wholeExperience = Mathf.FloorToInt(
fractionalExperience + 0.0001f);
fractionalExperience = Mathf.Max(
0f,
fractionalExperience - wholeExperience);
CurrentExperience += wholeExperience;
while (CurrentExperience >= RequiredExperience)
{
CurrentExperience -= RequiredExperience;
Level++;
OnLevelGained?.Invoke(Level);
}
Mathf.Max(0, amount));
progress.AddEvaluated(evaluatedAmount);
}
OnExperienceChanged?.Invoke(CurrentExperience, RequiredExperience, Level);
private void HandleProgressExperienceChanged(
int currentExperience,
int requiredExperience,
int level)
{
OnExperienceChanged?.Invoke(
currentExperience,
requiredExperience,
level);
}
private void HandleProgressLevelGained(int level)
{
OnLevelGained?.Invoke(level);
}
public static int RequiredExperienceForLevel(int level)
{
LevelUpConstants tuning = GameplayConstants.Current.LevelUp;
return tuning.BaseExperienceToNextLevel
+ (Mathf.Max(1, level) - 1)
* tuning.AdditionalExperiencePerLevel;
return ExperienceProgress.RequiredExperienceForLevel(level);
}
}
}
@@ -1,5 +1,7 @@
using BumpCombat.Player;
using BumpCombat.Core;
using System;
using System.Globalization;
using UnityEngine;
namespace BumpCombat.Progression
@@ -25,30 +27,109 @@ namespace BumpCombat.Progression
public int MaximumStacks => maximumStacks;
public float HealOnApply => healOnApply;
public string GetKoreanDisplayName()
{
return $"{GetKoreanStatName(stat)} 강화";
}
public string GetKoreanEffectDescription(float effectMultiplier)
{
string statName = GetKoreanStatName(stat);
string effectValue = operation == ModifierOperation.Flat
? FormatSignedValue(value * effectMultiplier)
: $"{FormatSignedValue(value * effectMultiplier * 100f)}%";
string description = $"{statName} {effectValue}";
if (healOnApply > 0f)
{
description += $"\n즉시 체력 회복 {FormatSignedValue(healOnApply * effectMultiplier)}";
}
return description + "\n선택하면 즉시 적용됩니다.";
}
private static string GetKoreanStatName(CharacterStat targetStat)
{
switch (targetStat)
{
case CharacterStat.CollisionDamage: return "몸통박치기 피해";
case CharacterStat.MoveSpeed: return "이동 속도";
case CharacterStat.MaxHealth: return "최대 체력";
case CharacterStat.Armor: return "방어력";
case CharacterStat.LifeSteal: return "생명력 흡수";
case CharacterStat.ExperienceGain: return "경험치 획득량";
case CharacterStat.ExperiencePickupRange: return "경험치 획득 범위";
case CharacterStat.ArtifactGaugeGain: return "유물 게이지 획득량";
case CharacterStat.IncomingDamage: return "받는 피해";
case CharacterStat.AttackDamage: return "공격 피해";
default: return "능력치";
}
}
private static string FormatSignedValue(float amount)
{
if (Mathf.Approximately(amount, 0f))
{
return "0";
}
string formatted = amount.ToString("0.##", CultureInfo.InvariantCulture);
return amount > 0f ? $"+{formatted}" : formatted;
}
public bool CanApply(PlayerStats playerStats)
{
return playerStats != null
&& playerStats.GetStackCount(modifierId, stat) < maximumStacks;
return CanApplyToModel(playerStats);
}
public bool CanApplyToModel(CharacterModel model)
{
return model != null
&& model.GetStackCount(modifierId, stat) < maximumStacks;
}
public bool TryApply(
PlayerStats playerStats,
PlayerHealth playerHealth)
{
if (!CanApply(playerStats)
|| !playerStats.AddModifier(new StatModifier(
Action<float> heal = playerHealth == null
? null
: amount => playerHealth.Heal(amount);
return TryApplyToModel(playerStats, heal);
}
public bool TryApplyToModel(
CharacterModel model,
Action<float> heal)
{
return TryApplyToModel(model, heal, 1f);
}
public bool TryApplyToModel(
CharacterModel model,
Action<float> heal,
float effectMultiplier)
{
if (float.IsNaN(effectMultiplier)
|| float.IsInfinity(effectMultiplier)
|| effectMultiplier < 0f)
{
return false;
}
if (!CanApplyToModel(model)
|| !model.AddModifier(new StatModifier(
modifierId,
stat,
operation,
value,
value * effectMultiplier,
maximumStacks)))
{
return false;
}
if (healOnApply > 0f && playerHealth != null)
if (healOnApply > 0f && heal != null)
{
playerHealth.Heal(healOnApply);
heal(healOnApply * effectMultiplier);
}
return true;
}