1f1100bbd8
- Add NanumGothic.ttf and NanumGothic SDF.asset for Korean text support - SongSelectManager: cardFont SerializeField for card title/artist rendering - SongDetailPanel: difficulty selected color changed from blue to green Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
231 lines
7.8 KiB
C#
231 lines
7.8 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.UI;
|
|
|
|
public class SongDetailPanel : MonoBehaviour
|
|
{
|
|
[Header("곡 정보")]
|
|
[SerializeField] private TMP_Text titleText;
|
|
[SerializeField] private TMP_Text artistText;
|
|
[SerializeField] private TMP_Text infoText;
|
|
|
|
[Header("난이도 버튼")]
|
|
[SerializeField] private Button btnNormal;
|
|
[SerializeField] private Button btnHard;
|
|
[SerializeField] private Button btnExpert;
|
|
[SerializeField] private Button btnExpertPlus;
|
|
|
|
[Header("액션 버튼")]
|
|
[SerializeField] private Button downloadButton;
|
|
[SerializeField] private Button deleteButton;
|
|
[SerializeField] private Button playButton;
|
|
[SerializeField] private Button closeButton;
|
|
|
|
[Header("진행률")]
|
|
[SerializeField] private GameObject progressGroup;
|
|
[SerializeField] private Slider progressSlider;
|
|
[SerializeField] private TMP_Text progressText;
|
|
|
|
[Header("씬 이름")]
|
|
[SerializeField] private string gameSceneName = "Game";
|
|
|
|
private static readonly Color SelectedColor = new Color(0.2f, 0.78f, 0.4f);
|
|
private static readonly Color DeselectedColor = Color.white;
|
|
|
|
private SongInfo currentSong;
|
|
private string selectedDifficulty;
|
|
private DownloadManager downloadManager;
|
|
private SongSelectManager selectManager;
|
|
|
|
private readonly (string key, Func<SongDetailPanel, Button> btn)[] diffSlots =
|
|
{
|
|
("normal", p => p.btnNormal),
|
|
("hard", p => p.btnHard),
|
|
("expert", p => p.btnExpert),
|
|
("expertplus", p => p.btnExpertPlus),
|
|
};
|
|
|
|
// ── Public API ───────────────────────────────────────────
|
|
|
|
public void Show(SongInfo song, DownloadManager dm, SongSelectManager sm)
|
|
{
|
|
currentSong = song;
|
|
downloadManager = dm;
|
|
selectManager = sm;
|
|
selectedDifficulty = null;
|
|
|
|
titleText.text = song.title;
|
|
artistText.text = song.artist;
|
|
infoText.text = song.duration > 0
|
|
? $"BPM {Mathf.RoundToInt(song.bpm)} | {FormatDuration(song.duration)}"
|
|
: $"BPM {Mathf.RoundToInt(song.bpm)}";
|
|
|
|
RefreshUI();
|
|
}
|
|
|
|
// ── UI 갱신 ──────────────────────────────────────────────
|
|
|
|
private void RefreshUI()
|
|
{
|
|
bool downloaded = SongLibrary.Instance.IsSongDownloaded(currentSong.id);
|
|
|
|
foreach (var (key, getBtn) in diffSlots)
|
|
{
|
|
Button btn = getBtn(this);
|
|
bool exists = currentSong.difficulties.Get(key) != null;
|
|
|
|
btn.interactable = downloaded && exists;
|
|
btn.onClick.RemoveAllListeners();
|
|
|
|
if (downloaded && exists)
|
|
{
|
|
string captured = key;
|
|
btn.onClick.AddListener(() => SelectDifficulty(captured));
|
|
}
|
|
}
|
|
|
|
UpdateDiffColors();
|
|
|
|
downloadButton.gameObject.SetActive(!downloaded);
|
|
deleteButton.gameObject.SetActive(downloaded);
|
|
playButton.interactable = downloaded && selectedDifficulty != null;
|
|
progressGroup.SetActive(false);
|
|
|
|
downloadButton.onClick.RemoveAllListeners();
|
|
downloadButton.onClick.AddListener(OnDownloadClicked);
|
|
|
|
deleteButton.onClick.RemoveAllListeners();
|
|
deleteButton.onClick.AddListener(OnDeleteClicked);
|
|
|
|
playButton.onClick.RemoveAllListeners();
|
|
playButton.onClick.AddListener(OnPlayClicked);
|
|
|
|
if (closeButton != null)
|
|
{
|
|
closeButton.onClick.RemoveAllListeners();
|
|
closeButton.onClick.AddListener(() => gameObject.SetActive(false));
|
|
}
|
|
}
|
|
|
|
private void SelectDifficulty(string difficulty)
|
|
{
|
|
selectedDifficulty = difficulty;
|
|
playButton.interactable = true;
|
|
UpdateDiffColors();
|
|
}
|
|
|
|
private void UpdateDiffColors()
|
|
{
|
|
foreach (var (key, getBtn) in diffSlots)
|
|
{
|
|
Button btn = getBtn(this);
|
|
var colors = btn.colors;
|
|
colors.normalColor = (key == selectedDifficulty) ? SelectedColor : DeselectedColor;
|
|
btn.colors = colors;
|
|
}
|
|
}
|
|
|
|
// ── 다운로드 ──────────────────────────────────────────────
|
|
|
|
private void OnDownloadClicked()
|
|
{
|
|
StartCoroutine(DownloadAllCoroutine());
|
|
}
|
|
|
|
private IEnumerator DownloadAllCoroutine()
|
|
{
|
|
var diffs = new List<string>();
|
|
foreach (var (key, _) in diffSlots)
|
|
if (currentSong.difficulties.Get(key) != null)
|
|
diffs.Add(key);
|
|
|
|
if (diffs.Count == 0) yield break;
|
|
|
|
SetInteractable(false);
|
|
progressGroup.SetActive(true);
|
|
downloadButton.gameObject.SetActive(false);
|
|
deleteButton.gameObject.SetActive(false);
|
|
playButton.gameObject.SetActive(false);
|
|
|
|
int totalSteps = diffs.Count;
|
|
int doneSteps = 0;
|
|
bool failed = false;
|
|
|
|
foreach (string diff in diffs)
|
|
{
|
|
bool stepDone = false;
|
|
|
|
downloadManager.DownloadSong(
|
|
currentSong, diff,
|
|
onProgress: p =>
|
|
{
|
|
float overall = (doneSteps + p) / totalSteps;
|
|
progressSlider.value = overall;
|
|
progressText.text = $"{diffs[Mathf.Min(doneSteps, diffs.Count - 1)].ToUpper()} {(int)(overall * 100)}%";
|
|
},
|
|
onComplete: () =>
|
|
{
|
|
SongLibrary.Instance.MarkDownloaded(currentSong.id, diff);
|
|
doneSteps++;
|
|
stepDone = true;
|
|
},
|
|
onError: err =>
|
|
{
|
|
Debug.LogError($"[SongDetailPanel] {err}");
|
|
failed = true;
|
|
stepDone = true;
|
|
});
|
|
|
|
yield return new WaitUntil(() => stepDone);
|
|
if (failed) break;
|
|
}
|
|
|
|
SetInteractable(true);
|
|
progressGroup.SetActive(false);
|
|
playButton.gameObject.SetActive(true);
|
|
selectManager.RefreshCards();
|
|
RefreshUI();
|
|
|
|
if (!failed)
|
|
Debug.Log($"[SongDetailPanel] '{currentSong.title}' 전체 다운로드 완료");
|
|
}
|
|
|
|
// ── 삭제 ─────────────────────────────────────────────────
|
|
|
|
private void OnDeleteClicked()
|
|
{
|
|
downloadManager.DeleteSong(currentSong.id);
|
|
SongLibrary.Instance.MarkSongRemoved(currentSong.id);
|
|
selectedDifficulty = null;
|
|
selectManager.RefreshCards();
|
|
RefreshUI();
|
|
}
|
|
|
|
// ── 플레이 ───────────────────────────────────────────────
|
|
|
|
private void OnPlayClicked()
|
|
{
|
|
GameSession.SelectedSong = currentSong;
|
|
GameSession.SelectedDifficulty = selectedDifficulty;
|
|
SceneManager.LoadScene(gameSceneName);
|
|
}
|
|
|
|
// ── 유틸 ─────────────────────────────────────────────────
|
|
|
|
private void SetInteractable(bool value)
|
|
{
|
|
downloadButton.interactable = value;
|
|
deleteButton.interactable = value;
|
|
playButton.interactable = value && selectedDifficulty != null;
|
|
foreach (var (_, getBtn) in diffSlots)
|
|
getBtn(this).interactable = value;
|
|
}
|
|
|
|
private static string FormatDuration(int seconds)
|
|
=> $"{seconds / 60}:{seconds % 60:D2}";
|
|
}
|