1042 lines
32 KiB
C#
1042 lines
32 KiB
C#
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using TMPro;
|
|
using System;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using GameServer.Client;
|
|
using DG.Tweening;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
public class SongDetailsUI : MonoBehaviour
|
|
{
|
|
private const string BackSceneName = "selectYourSongFirst";
|
|
private const float ProgressNumberMinX = -370f;
|
|
private const float ProgressNumberMaxX = 362f;
|
|
|
|
[Header("this_song_SO")]
|
|
public ScriptableObject this_song_SO;
|
|
|
|
[Header("ranking")]
|
|
public Button rankingButton;
|
|
public rankingList rkl;
|
|
|
|
[Header("room")]
|
|
public Button roomButton;
|
|
public GameObject askPrefab;
|
|
public GameObject roomDetailsPrefab;
|
|
public GameObject roomRankingPrefab;
|
|
public Transform roomSpawnPoint;
|
|
|
|
[Header("basic UI")]
|
|
public Image song_bgImage;
|
|
|
|
public Text songNameText;
|
|
public Text artistNameText;
|
|
public Text painterNameText;
|
|
public Text difficultyText;
|
|
public Text difficultyDesciptionText;
|
|
public Text charterNameText;
|
|
public Text dlcNameText;
|
|
public TextMeshProUGUI bpmText;
|
|
public Text songSerialNumberText;
|
|
public Text personalRecordText;
|
|
|
|
// Documentation text normalized.
|
|
public Text chartScoreText;
|
|
|
|
public Text thisSong_progressText;
|
|
public Text thisSong_lastScoreText; // will show last run total score if available
|
|
public Text idolRecordText;
|
|
public TextMeshProUGUI timeSpentText;
|
|
public TextMeshProUGUI enterTimeText;
|
|
public Text difficultyIdText;
|
|
public Image progressImage;
|
|
public GameObject progressNumberObject;
|
|
public TextMeshProUGUI progressPercentText;
|
|
public Text progressPercentText2;
|
|
|
|
public Image rankLevel_img;
|
|
public RankConfig rc;
|
|
//public Image songImage;
|
|
|
|
public int id_of_thisSong;
|
|
|
|
[Header("Inspector")]
|
|
public GameObject picDetail;
|
|
public Image profound_image;
|
|
public Text detail_informations_text;
|
|
|
|
[Header("back to last scene")]
|
|
[SerializeField] private Button backtoLastScene;
|
|
|
|
[Header("Inspector")]
|
|
public Button Button_EZ;
|
|
public Button Button_HD;
|
|
public Button Button_IN;
|
|
public Button Button_IM;
|
|
|
|
private SongData currentSong;
|
|
private GameObject spawnedAskInstance;
|
|
private GameObject spawnedRoomDetailsInstance;
|
|
private SongData lastSyncedSong;
|
|
private int lastSyncedDifficultyId = int.MinValue;
|
|
private UnityAction difficultyEzAction;
|
|
private UnityAction difficultyHdAction;
|
|
private UnityAction difficultyInAction;
|
|
private UnityAction difficultyImAction;
|
|
private RectTransform progressImageRect;
|
|
private RectTransform progressNumberRect;
|
|
private float progressImageFullWidth = -1f;
|
|
private float progressImageLeftEdgeX;
|
|
private float progressImageScaleX = 1f;
|
|
private bool progressUsesMaskWidthMode;
|
|
[SerializeField] private float progressTweenDuration = 0.35f;
|
|
[SerializeField] private Ease progressTweenEase = Ease.OutCubic;
|
|
|
|
private Tween progressBarTween;
|
|
private Tween progressNumberTween;
|
|
private Tween progressTextTween;
|
|
private float currentProgressVisual;
|
|
private float currentProgressTextValue;
|
|
private bool hasStarted;
|
|
|
|
private SongData ResolveCurrentSong()
|
|
{
|
|
if (SongDataHolder.SelectedSongData != null)
|
|
{
|
|
currentSong = SongDataHolder.SelectedSongData;
|
|
return currentSong;
|
|
}
|
|
|
|
if (currentSong != null)
|
|
{
|
|
return currentSong;
|
|
}
|
|
|
|
if (this_song_SO is SongData soSong)
|
|
{
|
|
currentSong = soSong;
|
|
return currentSong;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
currentSong = ResolveCurrentSong();
|
|
CacheProgressUiGeometry(forceRefresh: false);
|
|
|
|
ArenaRoomService roomService = ArenaRoomService.Instance;
|
|
if (roomService != null)
|
|
{
|
|
roomService.OnRoomSnapshotChanged -= HandleArenaRoomSnapshotChanged;
|
|
roomService.OnRoomSnapshotChanged += HandleArenaRoomSnapshotChanged;
|
|
HandleArenaRoomSnapshotChanged(roomService.CurrentRoom);
|
|
}
|
|
|
|
if (roomButton == null)
|
|
{
|
|
roomButton = FindButtonByName("roomButton");
|
|
}
|
|
|
|
if (rankingButton != null)
|
|
{
|
|
rankingButton.onClick.RemoveListener(OnRankingButtonClicked);
|
|
rankingButton.onClick.AddListener(OnRankingButtonClicked);
|
|
}
|
|
|
|
if (roomButton != null)
|
|
{
|
|
roomButton.onClick.RemoveListener(OnRoomButtonClicked);
|
|
roomButton.onClick.AddListener(OnRoomButtonClicked);
|
|
}
|
|
|
|
if (backtoLastScene != null)
|
|
{
|
|
backtoLastScene.onClick.RemoveListener(OnBackToLastSceneClicked);
|
|
backtoLastScene.onClick.AddListener(OnBackToLastSceneClicked);
|
|
}
|
|
|
|
BindDifficultyButtons();
|
|
PrepareDifficultyButtons();
|
|
|
|
if (hasStarted)
|
|
{
|
|
ResetProgressVisualState();
|
|
SyncSongDisplay(true);
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
SyncSongDisplay(false);
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
KillProgressTweens();
|
|
|
|
ArenaRoomService roomService = ArenaRoomService.Instance;
|
|
if (roomService != null)
|
|
{
|
|
roomService.OnRoomSnapshotChanged -= HandleArenaRoomSnapshotChanged;
|
|
}
|
|
|
|
if (rankingButton != null)
|
|
{
|
|
rankingButton.onClick.RemoveListener(OnRankingButtonClicked);
|
|
}
|
|
|
|
if (roomButton != null)
|
|
{
|
|
roomButton.onClick.RemoveListener(OnRoomButtonClicked);
|
|
}
|
|
|
|
if (backtoLastScene != null)
|
|
{
|
|
backtoLastScene.onClick.RemoveListener(OnBackToLastSceneClicked);
|
|
}
|
|
|
|
UnbindDifficultyButtons();
|
|
}
|
|
|
|
private void HandleArenaRoomSnapshotChanged(ArenaRoomSnapshot snapshot)
|
|
{
|
|
if (snapshot == null || string.IsNullOrWhiteSpace(snapshot.song_id))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!int.TryParse(snapshot.song_id, out int songId))
|
|
{
|
|
Debug.LogWarning($"[SongDetailsUI] Invalid arena room song_id: {snapshot.song_id}");
|
|
return;
|
|
}
|
|
|
|
SongDataLibrary library = SongDataLibrary.Instance;
|
|
if (library == null)
|
|
{
|
|
Debug.LogWarning("[SongDetailsUI] SongDataLibrary is not initialized.");
|
|
return;
|
|
}
|
|
|
|
SongData roomSong = library.GetSongDataByID(songId);
|
|
if (roomSong == null)
|
|
{
|
|
Debug.LogWarning($"[SongDetailsUI] Room song not found for song_id={songId}");
|
|
return;
|
|
}
|
|
|
|
int roomDifficultyId = ConvertDifficultyKeyToId(snapshot.difficulty);
|
|
bool sameSong = currentSong != null && currentSong.songID == roomSong.songID;
|
|
bool sameDifficulty = sameSong && currentSong.thisLevel_selectedDifficultyID == roomDifficultyId;
|
|
if (sameSong && sameDifficulty)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ApplySongSelection(roomSong, roomDifficultyId);
|
|
}
|
|
|
|
private void ApplySongSelection(SongData song, int difficultyId)
|
|
{
|
|
if (song == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
difficultyId = Mathf.Clamp(difficultyId, 0, 3);
|
|
song.thisLevel_selectedDifficultyID = difficultyId;
|
|
currentSong = song;
|
|
this_song_SO = song;
|
|
SongDataHolder.SelectedSongData = song;
|
|
id_of_thisSong = song.songID;
|
|
|
|
SyncSongDisplay(true);
|
|
}
|
|
|
|
private void SyncSongDisplay(bool force)
|
|
{
|
|
SongData song = ResolveCurrentSong();
|
|
if (song == null)
|
|
{
|
|
if (force || lastSyncedSong != null)
|
|
{
|
|
SetDifficultyButtons(-1);
|
|
if (difficultyText != null) difficultyText.text = string.Empty;
|
|
if (difficultyDesciptionText != null) difficultyDesciptionText.text = string.Empty;
|
|
if (difficultyIdText != null) difficultyIdText.text = string.Empty;
|
|
if (personalRecordText != null) personalRecordText.text = "0";
|
|
if (idolRecordText != null) idolRecordText.text = "0";
|
|
if (chartScoreText != null) chartScoreText.text = "0";
|
|
if (thisSong_lastScoreText != null) thisSong_lastScoreText.text = "0";
|
|
if (rankLevel_img != null && rc != null) rankLevel_img.sprite = rc.defaultSprite;
|
|
if (progressImage != null) progressImage.fillAmount = 0f;
|
|
UpdateProgressUI(0f);
|
|
}
|
|
|
|
lastSyncedSong = null;
|
|
lastSyncedDifficultyId = int.MinValue;
|
|
return;
|
|
}
|
|
|
|
currentSong = song;
|
|
this_song_SO = song;
|
|
id_of_thisSong = song.songID;
|
|
|
|
int selectedDifficulty = Mathf.Clamp(song.thisLevel_selectedDifficultyID, 0, 3);
|
|
bool songChanged = lastSyncedSong != song;
|
|
bool difficultyChanged = lastSyncedDifficultyId != selectedDifficulty;
|
|
|
|
if (force || songChanged)
|
|
{
|
|
RefreshSongPresentation(song);
|
|
}
|
|
|
|
if (force || songChanged || difficultyChanged)
|
|
{
|
|
SetDifficultyButtons(selectedDifficulty);
|
|
UpdateDifficultyUI(selectedDifficulty);
|
|
}
|
|
|
|
lastSyncedSong = song;
|
|
lastSyncedDifficultyId = selectedDifficulty;
|
|
}
|
|
|
|
private void RefreshSongPresentation(SongData song)
|
|
{
|
|
if (song == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (song_bgImage != null) song_bgImage.sprite = song.GetResolvedBackgroundPic();
|
|
if (songNameText != null) songNameText.text = song.songName;
|
|
if (artistNameText != null) artistNameText.text = song.artistName;
|
|
if (painterNameText != null) painterNameText.text = song.painter;
|
|
if (charterNameText != null) charterNameText.text = song.level_creator;
|
|
if (dlcNameText != null) dlcNameText.text = song.belongsTo_whichDLC;
|
|
if (songSerialNumberText != null) songSerialNumberText.text = song.songID.ToString();
|
|
if (bpmText != null) bpmText.text = LocalizationService.GetFormat("song.details.bpm", song.bpm);
|
|
if (profound_image != null) profound_image.sprite = song.GetResolvedFullscreenSongPicture();
|
|
if (timeSpentText != null) timeSpentText.text = LocalizationService.GetFormat("song.details.play_time", song.time_totalPlayingTime.ToString("F0"));
|
|
if (enterTimeText != null) enterTimeText.text = LocalizationService.GetFormat("song.details.play_count", song.game_enterTimes);
|
|
if (detail_informations_text != null)
|
|
{
|
|
detail_informations_text.text = LocalizationService.GetFormat("song.details.information",
|
|
song.artistName, song.songName, song.painter, song.level_creator, song.belongsTo_whichDLC);
|
|
}
|
|
}
|
|
|
|
private int ConvertDifficultyKeyToId(string difficultyKey)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(difficultyKey))
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
switch (difficultyKey.Trim().ToLowerInvariant())
|
|
{
|
|
case "ez":
|
|
return 0;
|
|
case "hd":
|
|
return 1;
|
|
case "im":
|
|
return 3;
|
|
case "in":
|
|
default:
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
hasStarted = true;
|
|
picDetail.SetActive(false);
|
|
CacheProgressUiGeometry(forceRefresh: false);
|
|
BindDifficultyButtons();
|
|
PrepareDifficultyButtons();
|
|
ResetProgressVisualState();
|
|
SyncSongDisplay(true);
|
|
}
|
|
|
|
public void OnRankingButtonClicked()
|
|
{
|
|
SongData song = ResolveCurrentSong();
|
|
if (song == null)
|
|
{
|
|
Debug.LogWarning("[SongDetailsUI] Ranking button clicked, but no song is currently selected.");
|
|
return;
|
|
}
|
|
|
|
if (rkl == null)
|
|
{
|
|
Debug.LogWarning("[SongDetailsUI] Ranking button clicked, but rankingList reference is missing.");
|
|
return;
|
|
}
|
|
|
|
rkl.Open(song.songID.ToString(), song.songName);
|
|
}
|
|
|
|
public void OnBackToLastSceneClicked()
|
|
{
|
|
if (!Bansonic.gTransition.LoadScene(BackSceneName, LoadSceneMode.Single))
|
|
{
|
|
SceneManager.LoadScene(BackSceneName, LoadSceneMode.Single);
|
|
}
|
|
}
|
|
|
|
public async void OnRoomButtonClicked()
|
|
{
|
|
SongData song = ResolveCurrentSong();
|
|
if (song == null)
|
|
{
|
|
Debug.LogWarning("[SongDetailsUI] Room button clicked, but no song is currently selected.");
|
|
Bansonic.gNotice.error.display("请先选择歌曲");
|
|
return;
|
|
}
|
|
|
|
ArenaRoomService roomService = ArenaRoomService.Instance;
|
|
if (roomService == null)
|
|
{
|
|
Debug.LogWarning("[SongDetailsUI] ArenaRoomService is missing.");
|
|
Bansonic.gNotice.error.display("房间服务尚未初始化,请稍后重试");
|
|
return;
|
|
}
|
|
|
|
if (roomService.IsInRoom)
|
|
{
|
|
try
|
|
{
|
|
await roomService.RefreshRoomInfo();
|
|
ShowRoomDetails(roomService);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[SongDetailsUI] Failed to refresh current room: {ex.Message}");
|
|
NetworkManager.ShowUserFacingNetworkError(ex.Message);
|
|
DestroyRoomDetailsInstance();
|
|
ShowRoomAsk(song, roomService);
|
|
}
|
|
return;
|
|
}
|
|
|
|
DestroyRoomDetailsInstance();
|
|
ShowRoomAsk(song, roomService);
|
|
}
|
|
|
|
public void OpenRoomDetailsForCurrentRoom()
|
|
{
|
|
ArenaRoomService roomService = ArenaRoomService.Instance;
|
|
if (roomService == null || !roomService.IsInRoom)
|
|
{
|
|
return;
|
|
}
|
|
|
|
HandleArenaRoomSnapshotChanged(roomService.CurrentRoom);
|
|
DestroyAskInstance();
|
|
ShowRoomDetails(roomService);
|
|
}
|
|
|
|
private void ShowRoomAsk(SongData song, ArenaRoomService roomService)
|
|
{
|
|
if (spawnedAskInstance != null)
|
|
{
|
|
Destroy(spawnedAskInstance);
|
|
}
|
|
|
|
GameObject askTemplate = ResolveAskTemplate();
|
|
if (askTemplate == null)
|
|
{
|
|
Debug.LogWarning("[SongDetailsUI] Ask prefab/template is missing.");
|
|
return;
|
|
}
|
|
|
|
spawnedAskInstance = Instantiate(askTemplate, ResolveRoomSpawnParent(), false);
|
|
PrepareSpawnedPanel(spawnedAskInstance);
|
|
RegisterManagedOverlay(spawnedAskInstance, DestroyAskInstance);
|
|
|
|
roomAsk ask = spawnedAskInstance.GetComponent<roomAsk>();
|
|
if (ask == null)
|
|
{
|
|
Debug.LogWarning("[SongDetailsUI] Spawned ask object has no roomAsk component.");
|
|
return;
|
|
}
|
|
|
|
ask.Bind(
|
|
async () =>
|
|
{
|
|
await roomService.CreateRoom(
|
|
song.songID.ToString(),
|
|
song.songName,
|
|
ResolveSelectedDifficultyKey(song),
|
|
null);
|
|
try
|
|
{
|
|
await roomService.RefreshRoomInfo();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[SongDetailsUI] Refresh room info after create failed: {ex.Message}");
|
|
}
|
|
DestroyAskInstance();
|
|
ShowRoomDetails(roomService);
|
|
},
|
|
async roomCode =>
|
|
{
|
|
await roomService.JoinRoom(roomCode, null);
|
|
try
|
|
{
|
|
await roomService.RefreshRoomInfo();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"[SongDetailsUI] Refresh room info after join failed: {ex.Message}");
|
|
}
|
|
DestroyAskInstance();
|
|
ShowRoomDetails(roomService);
|
|
});
|
|
}
|
|
|
|
private void ShowRoomDetails(ArenaRoomService roomService)
|
|
{
|
|
if (spawnedRoomDetailsInstance != null)
|
|
{
|
|
roomDetails existing = spawnedRoomDetailsInstance.GetComponent<roomDetails>();
|
|
if (existing != null)
|
|
{
|
|
existing.ConfigureRoomRanking(roomRankingPrefab, ResolveRoomSpawnParent());
|
|
existing.Bind(roomService);
|
|
RegisterManagedOverlay(spawnedRoomDetailsInstance, DestroyRoomDetailsInstance);
|
|
}
|
|
return;
|
|
}
|
|
|
|
GameObject roomTemplate = ResolveRoomDetailsTemplate();
|
|
if (roomTemplate == null)
|
|
{
|
|
Debug.LogWarning("[SongDetailsUI] Room details prefab/template is missing.");
|
|
return;
|
|
}
|
|
|
|
spawnedRoomDetailsInstance = Instantiate(roomTemplate, ResolveRoomSpawnParent(), false);
|
|
PrepareSpawnedPanel(spawnedRoomDetailsInstance);
|
|
RegisterManagedOverlay(spawnedRoomDetailsInstance, DestroyRoomDetailsInstance);
|
|
|
|
roomDetails details = spawnedRoomDetailsInstance.GetComponent<roomDetails>();
|
|
if (details == null)
|
|
{
|
|
Debug.LogWarning("[SongDetailsUI] Spawned room details object has no roomDetails component.");
|
|
return;
|
|
}
|
|
|
|
details.ConfigureRoomRanking(roomRankingPrefab, ResolveRoomSpawnParent());
|
|
details.Bind(roomService);
|
|
}
|
|
|
|
private void DestroyAskInstance()
|
|
{
|
|
if (spawnedAskInstance != null)
|
|
{
|
|
UIBackStack.Unregister(spawnedAskInstance);
|
|
Destroy(spawnedAskInstance);
|
|
spawnedAskInstance = null;
|
|
}
|
|
}
|
|
|
|
private void DestroyRoomDetailsInstance()
|
|
{
|
|
if (spawnedRoomDetailsInstance != null)
|
|
{
|
|
UIBackStack.Unregister(spawnedRoomDetailsInstance);
|
|
Destroy(spawnedRoomDetailsInstance);
|
|
spawnedRoomDetailsInstance = null;
|
|
}
|
|
}
|
|
|
|
private GameObject ResolveAskTemplate()
|
|
{
|
|
if (askPrefab != null)
|
|
{
|
|
return askPrefab;
|
|
}
|
|
|
|
roomAsk sceneTemplate = FindSceneObject<roomAsk>();
|
|
if (sceneTemplate != null)
|
|
{
|
|
sceneTemplate.gameObject.SetActive(false);
|
|
return sceneTemplate.gameObject;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private GameObject ResolveRoomDetailsTemplate()
|
|
{
|
|
if (roomDetailsPrefab != null)
|
|
{
|
|
return roomDetailsPrefab;
|
|
}
|
|
|
|
roomDetails sceneTemplate = FindSceneObject<roomDetails>();
|
|
if (sceneTemplate != null)
|
|
{
|
|
sceneTemplate.gameObject.SetActive(false);
|
|
return sceneTemplate.gameObject;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private Transform ResolveRoomSpawnParent()
|
|
{
|
|
if (roomSpawnPoint != null && roomSpawnPoint.gameObject.activeInHierarchy && roomSpawnPoint.lossyScale.sqrMagnitude > 0.0001f)
|
|
{
|
|
return roomSpawnPoint;
|
|
}
|
|
|
|
Canvas canvas = null;
|
|
if (roomButton != null)
|
|
{
|
|
canvas = roomButton.GetComponentsInParent<Canvas>(true).FirstOrDefault();
|
|
}
|
|
|
|
if (canvas == null)
|
|
{
|
|
canvas = GetComponentsInParent<Canvas>(true).FirstOrDefault();
|
|
}
|
|
|
|
if (canvas != null)
|
|
{
|
|
return canvas.transform;
|
|
}
|
|
|
|
return transform;
|
|
}
|
|
|
|
private void PrepareSpawnedPanel(GameObject panel)
|
|
{
|
|
if (panel == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
panel.SetActive(true);
|
|
RectTransform rect = panel.GetComponent<RectTransform>();
|
|
if (rect != null)
|
|
{
|
|
rect.localScale = Vector3.one;
|
|
rect.anchoredPosition3D = Vector3.zero;
|
|
}
|
|
|
|
if (panel.GetComponent<UI_PanelPopupTween>() == null)
|
|
{
|
|
panel.AddComponent<UI_PanelPopupTween>();
|
|
}
|
|
}
|
|
|
|
private void RegisterManagedOverlay(GameObject panel, Action closeAction)
|
|
{
|
|
if (panel == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UIBackStack.RegisterOrBump(
|
|
panel,
|
|
() => panel != null && panel.activeInHierarchy,
|
|
() =>
|
|
{
|
|
closeAction?.Invoke();
|
|
return true;
|
|
});
|
|
}
|
|
|
|
private Button FindButtonByName(string objectName)
|
|
{
|
|
Button[] buttons = Resources.FindObjectsOfTypeAll<Button>();
|
|
return buttons.FirstOrDefault(b => b != null
|
|
&& b.gameObject.scene.IsValid()
|
|
&& string.Equals(b.gameObject.name, objectName, StringComparison.Ordinal));
|
|
}
|
|
|
|
private static T FindSceneObject<T>() where T : Component
|
|
{
|
|
T[] all = Resources.FindObjectsOfTypeAll<T>();
|
|
return all.FirstOrDefault(item => item != null && item.gameObject.scene.IsValid());
|
|
}
|
|
|
|
private string ResolveSelectedDifficultyKey(SongData song)
|
|
{
|
|
int selectedDifficulty = song != null ? song.thisLevel_selectedDifficultyID : 2;
|
|
return selectedDifficulty switch
|
|
{
|
|
0 => "ez",
|
|
1 => "hd",
|
|
3 => "im",
|
|
_ => "in",
|
|
};
|
|
}
|
|
|
|
private void SetDifficultyButtons(int selectedDifficulty)
|
|
{
|
|
SetDifficultyButtonAlpha(Button_EZ, selectedDifficulty == 0);
|
|
SetDifficultyButtonAlpha(Button_HD, selectedDifficulty == 1);
|
|
SetDifficultyButtonAlpha(Button_IN, selectedDifficulty == 2);
|
|
SetDifficultyButtonAlpha(Button_IM, selectedDifficulty == 3);
|
|
}
|
|
|
|
private void BindDifficultyButtons()
|
|
{
|
|
BindDifficultyButton(Button_EZ, 0, ref difficultyEzAction);
|
|
BindDifficultyButton(Button_HD, 1, ref difficultyHdAction);
|
|
BindDifficultyButton(Button_IN, 2, ref difficultyInAction);
|
|
BindDifficultyButton(Button_IM, 3, ref difficultyImAction);
|
|
}
|
|
|
|
private void UnbindDifficultyButtons()
|
|
{
|
|
UnbindDifficultyButton(Button_EZ, difficultyEzAction);
|
|
UnbindDifficultyButton(Button_HD, difficultyHdAction);
|
|
UnbindDifficultyButton(Button_IN, difficultyInAction);
|
|
UnbindDifficultyButton(Button_IM, difficultyImAction);
|
|
}
|
|
|
|
private void BindDifficultyButton(Button button, int difficultyId, ref UnityAction cachedAction)
|
|
{
|
|
if (button == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cachedAction != null)
|
|
{
|
|
button.onClick.RemoveListener(cachedAction);
|
|
}
|
|
|
|
cachedAction = () => OnDifficultySelected(difficultyId);
|
|
button.onClick.AddListener(cachedAction);
|
|
}
|
|
|
|
private void UnbindDifficultyButton(Button button, UnityAction cachedAction)
|
|
{
|
|
if (button == null || cachedAction == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
button.onClick.RemoveListener(cachedAction);
|
|
}
|
|
|
|
private void PrepareDifficultyButtons()
|
|
{
|
|
ForceDifficultyButtonVisualMode(Button_EZ);
|
|
ForceDifficultyButtonVisualMode(Button_HD);
|
|
ForceDifficultyButtonVisualMode(Button_IN);
|
|
ForceDifficultyButtonVisualMode(Button_IM);
|
|
}
|
|
|
|
private void ForceDifficultyButtonVisualMode(Button button)
|
|
{
|
|
if (button == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
button.transition = Selectable.Transition.None;
|
|
}
|
|
|
|
private void SetDifficultyButtonAlpha(Button button, bool active)
|
|
{
|
|
if (button == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Graphic graphic = button.targetGraphic != null ? button.targetGraphic : button.image;
|
|
if (graphic == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
graphic.DOKill(false);
|
|
graphic.DOFade(active ? 1f : 0f, 0.18f).SetEase(Ease.OutCubic);
|
|
}
|
|
|
|
private void OnDifficultySelected(int difficulty)
|
|
{
|
|
if (currentSong != null)
|
|
{
|
|
currentSong.thisLevel_selectedDifficultyID = difficulty;
|
|
SetDifficultyButtons(difficulty);
|
|
UpdateDifficultyUI(difficulty);
|
|
}
|
|
}
|
|
|
|
private void UpdateDifficultyUI(int difficulty)
|
|
{
|
|
if (currentSong == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var entry = GetChartEntry(difficulty);
|
|
if (entry != null)
|
|
{
|
|
if (difficultyText != null)
|
|
difficultyText.text = entry.difficultyLEVEL.ToString("0.0");
|
|
if (difficultyDesciptionText != null)
|
|
difficultyDesciptionText.text = string.IsNullOrEmpty(entry.difficultyDescription) ? entry.difficultyName : entry.difficultyDescription;
|
|
|
|
if (difficultyIdText != null)
|
|
difficultyIdText.text = entry.difficultyLEVEL.ToString("0.0");
|
|
|
|
UpdateProgressUI(entry.levelProgressForThisDifficulty);
|
|
|
|
// read values from SongData (SO)
|
|
int chartPersonal = currentSong.GetPersonalRecord(difficulty); // Documentation text normalized.
|
|
int idol = currentSong.GetIdolRecord(difficulty); // Documentation text normalized.
|
|
|
|
// Determine total: prefer stored total if present (>0), otherwise compute chartPersonal + idol
|
|
int total;
|
|
if (entry.totalPersonalRecordForThisDifficulty > 0)
|
|
{
|
|
total = entry.totalPersonalRecordForThisDifficulty;
|
|
}
|
|
else
|
|
{
|
|
total = chartPersonal + idol;
|
|
}
|
|
|
|
// show total (personalRecord) and idol separately
|
|
personalRecordText.text = total.ToString();
|
|
if (idolRecordText != null)
|
|
idolRecordText.text = idol.ToString();
|
|
|
|
// Documentation text normalized.
|
|
if (chartScoreText != null)
|
|
chartScoreText.text = chartPersonal.ToString();
|
|
|
|
// show last run total score if UI text provided
|
|
if (thisSong_lastScoreText != null)
|
|
{
|
|
int lastRun = currentSong.GetChartScore(difficulty);
|
|
thisSong_lastScoreText.text = lastRun.ToString();
|
|
}
|
|
|
|
if (rankLevel_img != null && rc != null)
|
|
rankLevel_img.sprite = rc.GetRankSprite(total);
|
|
|
|
// also keep per-entry convenience fields in sync for inspector visibility
|
|
entry.chartPersonalRecordForThisDifficulty = chartPersonal;
|
|
entry.idolPersonalRecordForThisDifficulty = idol;
|
|
entry.totalPersonalRecordForThisDifficulty = total;
|
|
}
|
|
else
|
|
{
|
|
if (difficultyText != null)
|
|
difficultyText.text = difficulty.ToString();
|
|
if (difficultyIdText != null)
|
|
difficultyIdText.text = difficulty.ToString();
|
|
UpdateProgressUI(0f);
|
|
if (difficultyDesciptionText != null)
|
|
difficultyDesciptionText.text = "";
|
|
if (personalRecordText != null)
|
|
personalRecordText.text = "0";
|
|
if (idolRecordText != null)
|
|
idolRecordText.text = "0";
|
|
if (chartScoreText != null)
|
|
chartScoreText.text = "0";
|
|
if (thisSong_lastScoreText != null)
|
|
thisSong_lastScoreText.text = "0";
|
|
if (rankLevel_img != null && rc != null)
|
|
rankLevel_img.sprite = rc.defaultSprite;
|
|
}
|
|
}
|
|
|
|
private void UpdateProgressUI(float progress)
|
|
{
|
|
CacheProgressUiGeometry(forceRefresh: false);
|
|
|
|
// 2. Clamp for visual elements (fillAmount and Object position)
|
|
float clampedProgress = Mathf.Clamp01(progress);
|
|
|
|
AnimateProgressVisual(clampedProgress);
|
|
AnimateProgressText(progress);
|
|
}
|
|
|
|
private void CacheProgressUiGeometry(bool forceRefresh)
|
|
{
|
|
if (progressImage != null && (forceRefresh || progressImageRect == null))
|
|
{
|
|
progressImageRect = progressImage.rectTransform;
|
|
}
|
|
|
|
if (progressNumberObject != null && (forceRefresh || progressNumberRect == null))
|
|
{
|
|
progressNumberRect = progressNumberObject.GetComponent<RectTransform>();
|
|
}
|
|
|
|
if (progressImageRect == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (forceRefresh || progressImageFullWidth <= 0f)
|
|
{
|
|
progressImageFullWidth = progressImageRect.sizeDelta.x;
|
|
progressImageScaleX = Mathf.Abs(progressImageRect.localScale.x);
|
|
progressImageLeftEdgeX = progressImageRect.anchoredPosition.x - (progressImageFullWidth * progressImageScaleX * 0.5f);
|
|
}
|
|
|
|
progressUsesMaskWidthMode = progressImage != null
|
|
&& progressImage.GetComponent<Mask>() != null
|
|
&& progressImage.transform.childCount > 0;
|
|
}
|
|
|
|
private void ResetProgressVisualState()
|
|
{
|
|
CacheProgressUiGeometry(forceRefresh: false);
|
|
KillProgressTweens();
|
|
currentProgressVisual = 0f;
|
|
currentProgressTextValue = 0f;
|
|
ApplyProgressBarVisual(0f);
|
|
ApplyProgressNumberPosition(0f);
|
|
ApplyProgressTextValue(0f);
|
|
}
|
|
|
|
private void KillProgressTweens()
|
|
{
|
|
if (progressBarTween != null && progressBarTween.IsActive())
|
|
{
|
|
progressBarTween.Kill();
|
|
}
|
|
|
|
if (progressNumberTween != null && progressNumberTween.IsActive())
|
|
{
|
|
progressNumberTween.Kill();
|
|
}
|
|
|
|
if (progressTextTween != null && progressTextTween.IsActive())
|
|
{
|
|
progressTextTween.Kill();
|
|
}
|
|
}
|
|
|
|
private void AnimateProgressVisual(float clampedProgress)
|
|
{
|
|
KillProgressTweens();
|
|
|
|
progressBarTween = DOTween.To(
|
|
() => currentProgressVisual,
|
|
value =>
|
|
{
|
|
currentProgressVisual = value;
|
|
ApplyProgressBarVisual(value);
|
|
},
|
|
clampedProgress,
|
|
progressTweenDuration)
|
|
.SetEase(progressTweenEase)
|
|
.OnComplete(() =>
|
|
{
|
|
currentProgressVisual = clampedProgress;
|
|
ApplyProgressBarVisual(clampedProgress);
|
|
});
|
|
|
|
if (progressNumberRect != null)
|
|
{
|
|
progressNumberTween = progressNumberRect
|
|
.DOAnchorPosX(Mathf.Lerp(ProgressNumberMinX, ProgressNumberMaxX, clampedProgress), progressTweenDuration)
|
|
.SetEase(progressTweenEase);
|
|
}
|
|
}
|
|
|
|
private void AnimateProgressText(float targetProgressValue)
|
|
{
|
|
float safeTargetValue = Mathf.Max(0f, targetProgressValue);
|
|
|
|
progressTextTween = DOTween.To(
|
|
() => currentProgressTextValue,
|
|
value =>
|
|
{
|
|
currentProgressTextValue = value;
|
|
ApplyProgressTextValue(value);
|
|
},
|
|
safeTargetValue,
|
|
progressTweenDuration)
|
|
.SetEase(progressTweenEase)
|
|
.OnComplete(() =>
|
|
{
|
|
currentProgressTextValue = safeTargetValue;
|
|
ApplyProgressTextValue(safeTargetValue);
|
|
});
|
|
}
|
|
|
|
private void ApplyProgressNumberPosition(float clampedProgress)
|
|
{
|
|
if (progressNumberRect == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector2 anchoredPos = progressNumberRect.anchoredPosition;
|
|
anchoredPos.x = Mathf.Lerp(ProgressNumberMinX, ProgressNumberMaxX, clampedProgress);
|
|
progressNumberRect.anchoredPosition = anchoredPos;
|
|
}
|
|
|
|
private void ApplyProgressTextValue(float progressValue)
|
|
{
|
|
int percentValue = Mathf.FloorToInt(Mathf.Max(0f, progressValue) * 100f);
|
|
string textValue = percentValue.ToString() + "%";
|
|
|
|
if (progressPercentText != null)
|
|
{
|
|
progressPercentText.text = textValue;
|
|
}
|
|
|
|
if (progressPercentText2 != null)
|
|
{
|
|
progressPercentText2.text = textValue;
|
|
}
|
|
}
|
|
|
|
private void ApplyProgressBarVisual(float clampedProgress)
|
|
{
|
|
if (progressImage == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (progressUsesMaskWidthMode && progressImageRect != null && progressImageFullWidth > 0f)
|
|
{
|
|
float newWidth = progressImageFullWidth * clampedProgress;
|
|
progressImageRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, newWidth);
|
|
|
|
Vector2 anchoredPos = progressImageRect.anchoredPosition;
|
|
anchoredPos.x = progressImageLeftEdgeX + (newWidth * progressImageScaleX * 0.5f);
|
|
progressImageRect.anchoredPosition = anchoredPos;
|
|
return;
|
|
}
|
|
|
|
progressImage.fillAmount = clampedProgress;
|
|
}
|
|
|
|
private ChartFileEntry GetChartEntry(int difficulty)
|
|
{
|
|
if (currentSong == null || currentSong.chartFiles == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
foreach (var entry in currentSong.chartFiles)
|
|
{
|
|
if (entry != null && entry.difficulty == difficulty)
|
|
return entry;
|
|
}
|
|
return null;
|
|
}
|
|
}
|