Initial commit: Tiny Tackle Heroes Unity project

This commit is contained in:
2026-09-15 17:37:42 +09:00
commit 591fa9a826
1623 changed files with 182420 additions and 0 deletions
@@ -0,0 +1,254 @@
using System.Collections.Generic;
using BumpCombat.Audio;
using BumpCombat.Constants;
using BumpCombat.Core;
using BumpCombat.Player;
using BumpCombat.UI;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
namespace BumpCombat.Progression
{
public sealed class LevelUpController : MonoBehaviour
{
private const int MaximumSupportedOptions = 3;
[SerializeField] private GameObject panel;
[SerializeField] private Text[] optionTexts;
[System.NonSerialized] private ModifierDefinition[] modifierCatalog;
private readonly ModifierDefinition[] shownModifiers =
new ModifierDefinition[MaximumSupportedOptions];
private Image[] optionCardImages = System.Array.Empty<Image>();
private ExperienceSystem experienceSystem;
private PlayerStats playerStats;
private PlayerHealth playerHealth;
private int selectedIndex;
private int shownCount;
private int pendingSelections;
private int ignoreInputThroughFrame;
private void Start()
{
experienceSystem = FindAnyObjectByType<ExperienceSystem>();
if (experienceSystem == null || panel == null)
{
enabled = false;
return;
}
playerStats = experienceSystem.GetComponent<PlayerStats>();
playerHealth = experienceSystem.GetComponent<PlayerHealth>();
experienceSystem.OnLevelGained += QueueSelection;
if (RunManager.Instance != null)
{
RunManager.Instance.OnSelectionChanged += HandleSelectionChanged;
}
PresentationUiStyle.ApplyPanel(panel.GetComponent<Image>());
ApplySelectionTextStyle();
CreateCardBackdrops();
panel.SetActive(false);
}
private void OnDestroy()
{
if (experienceSystem != null)
{
experienceSystem.OnLevelGained -= QueueSelection;
}
if (RunManager.Instance != null)
{
RunManager.Instance.OnSelectionChanged -= HandleSelectionChanged;
}
}
private void Update()
{
if (!panel.activeSelf
|| shownCount == 0
|| Time.frameCount <= ignoreInputThroughFrame
|| Keyboard.current == null)
{
return;
}
if (Keyboard.current.leftArrowKey.wasPressedThisFrame
|| Keyboard.current.upArrowKey.wasPressedThisFrame)
{
selectedIndex = (selectedIndex + shownCount - 1) % shownCount;
BumpCombatAudioService.Instance?.PlayUi("ui_move");
UpdateSelectionVisual();
}
else if (Keyboard.current.rightArrowKey.wasPressedThisFrame
|| Keyboard.current.downArrowKey.wasPressedThisFrame)
{
selectedIndex = (selectedIndex + 1) % shownCount;
BumpCombatAudioService.Instance?.PlayUi("ui_move");
UpdateSelectionVisual();
}
else if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
bool applied = shownModifiers[selectedIndex].TryApply(
playerStats,
playerHealth);
if (applied)
{
BumpCombatAudioService.Instance?.PlayUi("ui_confirm");
}
panel.SetActive(false);
RunManager.Instance?.CloseSelection(this);
}
}
private void QueueSelection(int level)
{
pendingSelections++;
TryOpenPendingSelection();
}
private void TryOpenPendingSelection()
{
if (pendingSelections == 0
|| RunManager.Instance == null
|| !RunManager.Instance.TryOpenSelection(this))
{
return;
}
if (!BuildChoices())
{
pendingSelections--;
RunManager.Instance.CloseSelection(this);
return;
}
pendingSelections--;
selectedIndex = 0;
panel.SetActive(true);
ignoreInputThroughFrame = Time.frameCount;
UpdateSelectionVisual();
}
private bool BuildChoices()
{
List<ModifierDefinition> pool = new();
ModifierDefinition[] definitions =
modifierCatalog ?? GameplayConstants.Current.LevelUp.ModifierDefinitions;
if (definitions != null)
{
foreach (ModifierDefinition definition in definitions)
{
if (definition != null && definition.CanApply(playerStats))
{
pool.Add(definition);
}
}
}
shownCount = Mathf.Min(
Mathf.Clamp(
GameplayConstants.Current.LevelUp.OptionCount,
1,
MaximumSupportedOptions),
optionTexts?.Length ?? 0,
pool.Count);
for (int i = 0; i < shownCount; i++)
{
int choiceIndex = Random.Range(0, pool.Count);
shownModifiers[i] = pool[choiceIndex];
pool.RemoveAt(choiceIndex);
optionTexts[i].text = shownModifiers[i].DisplayName;
optionTexts[i].gameObject.SetActive(true);
}
for (int i = shownCount; i < (optionTexts?.Length ?? 0); i++)
{
optionTexts[i].gameObject.SetActive(false);
}
return shownCount > 0;
}
private void HandleSelectionChanged(bool isOpen)
{
if (!isOpen && !panel.activeSelf)
{
TryOpenPendingSelection();
}
}
private void UpdateSelectionVisual()
{
for (int i = 0; i < (optionTexts?.Length ?? 0); i++)
{
bool selected = i == selectedIndex && i < shownCount;
optionTexts[i].color = selected
? new Color(1f, 0.85f, 0.2f)
: Color.white;
if (i < optionCardImages.Length)
{
PresentationUiStyle.ApplyCard(
optionCardImages[i],
selected);
optionCardImages[i].gameObject.SetActive(
optionTexts[i].gameObject.activeSelf);
}
}
}
private void ApplySelectionTextStyle()
{
Text[] texts = panel.GetComponentsInChildren<Text>(true);
foreach (Text text in texts)
{
PresentationUiStyle.ApplyText(text, Mathf.Max(1, text.fontSize));
if (text.name == "Title")
{
text.horizontalOverflow = HorizontalWrapMode.Overflow;
text.verticalOverflow = VerticalWrapMode.Truncate;
text.resizeTextForBestFit = true;
text.resizeTextMinSize = 24;
text.resizeTextMaxSize = 38;
}
}
}
private void CreateCardBackdrops()
{
optionCardImages = new Image[optionTexts?.Length ?? 0];
for (int i = 0; i < optionCardImages.Length; i++)
{
Text optionText = optionTexts[i];
if (optionText == null)
{
continue;
}
optionCardImages[i] = PresentationUiStyle.CreateCardBackdrop(
optionText,
$"Option Card {i + 1}",
optionText.rectTransform.sizeDelta);
}
}
#if UNITY_EDITOR
public bool DebugIsSelectionVisible => panel != null && panel.activeSelf;
public void Configure(
GameObject selectionPanel,
Text[] options,
ModifierDefinition[] definitions)
{
panel = selectionPanel;
optionTexts = options;
modifierCatalog = definitions;
}
public ModifierDefinition GetShownModifier(int index)
{
return index >= 0 && index < shownCount
? shownModifiers[index]
: null;
}
#endif
}
}