Files
bansonic_beta_main/Assets/songsDatabase/SongDetailsUI.cs
T

723 lines
23 KiB
C#

using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System;
using System.Linq;
using System.Threading.Tasks;
using GameServer.Client;
public class SongDetailsUI : MonoBehaviour
{
[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 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("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 ResolveCurrentSong()
{
if (currentSong != null)
{
return currentSong;
}
if (this_song_SO is SongData soSong)
{
currentSong = soSong;
return currentSong;
}
if (SongDataHolder.SelectedSongData != null)
{
currentSong = SongDataHolder.SelectedSongData;
return currentSong;
}
return null;
}
private void OnEnable()
{
currentSong = ResolveCurrentSong();
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);
}
}
private void OnDisable()
{
ArenaRoomService roomService = ArenaRoomService.Instance;
if (roomService != null)
{
roomService.OnRoomSnapshotChanged -= HandleArenaRoomSnapshotChanged;
}
if (rankingButton != null)
{
rankingButton.onClick.RemoveListener(OnRankingButtonClicked);
}
if (roomButton != null)
{
roomButton.onClick.RemoveListener(OnRoomButtonClicked);
}
}
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;
RefreshSongPresentation(song);
SetDifficultyButtons(difficultyId);
UpdateDifficultyUI(difficultyId);
}
private void RefreshSongPresentation(SongData song)
{
if (song == null)
{
return;
}
if (song_bgImage != null) song_bgImage.sprite = song.backgroudPIC;
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 = "BPM : " + song.bpm.ToString();
if (profound_image != null) profound_image.sprite = song.fullscreen_songPicture;
if (timeSpentText != null) timeSpentText.text = "PlayTime: " + song.time_totalPlayingTime.ToString("F0") + "s";
if (enterTimeText != null) enterTimeText.text = "PlayCount: " + song.game_enterTimes.ToString();
if (detail_informations_text != null)
{
detail_informations_text.text =
$"{song.artistName} - {song.songName} | 曲绘: {song.painter} | 制谱: {song.level_creator} | DLC: {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()
{
if (SongDataHolder.SelectedSongData != null)
{
// Documentation text normalized.
currentSong = SongDataHolder.SelectedSongData;
this_song_SO = currentSong;
song_bgImage.sprite = currentSong.backgroudPIC;
// Documentation text normalized.
songNameText.text = currentSong.songName;
artistNameText.text = currentSong.artistName;
painterNameText.text = currentSong.painter;
charterNameText.text = currentSong.level_creator;
dlcNameText.text = currentSong.belongsTo_whichDLC;
songSerialNumberText.text = currentSong.songID.ToString();
bpmText.text = "BPM : " + currentSong.bpm.ToString();
profound_image.sprite = currentSong.fullscreen_songPicture;
if (timeSpentText != null) timeSpentText.text = "PlayTime: " + currentSong.time_totalPlayingTime.ToString("F0") + "s";
if (enterTimeText != null) enterTimeText.text = "PlayCount: " + currentSong.game_enterTimes.ToString();
id_of_thisSong = currentSong.songID;
detail_informations_text.text =
$"{currentSong.artistName}" + " - " +
$"{currentSong.songName}" + " | " +
$"曲绘: {currentSong.painter}" + " | " +
$"制谱: {currentSong.level_creator}" + " | " +
$"DLC: {currentSong.belongsTo_whichDLC}";
if (currentSong.illustration != null)
{
//songImage.sprite = currentSong.illustration;
//songImage.enabled = true;
}
else
{
Debug.LogWarning("歌曲封面为空:" + currentSong.songName);
}
Debug.Log("加载歌曲数据: " + currentSong.songName);
}
else
{
// Documentation text normalized.
}
picDetail.SetActive(false);
// Documentation text normalized.
if (currentSong != null)
{
SetDifficultyButtons(currentSong.thisLevel_selectedDifficultyID);
UpdateDifficultyUI(currentSong.thisLevel_selectedDifficultyID);
Button_EZ.onClick.AddListener(() => OnDifficultySelected(0));
Button_HD.onClick.AddListener(() => OnDifficultySelected(1));
Button_IN.onClick.AddListener(() => OnDifficultySelected(2));
Button_IM.onClick.AddListener(() => OnDifficultySelected(3));
}
}
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 async void OnRoomButtonClicked()
{
SongData song = ResolveCurrentSong();
if (song == null)
{
Debug.LogWarning("[SongDetailsUI] Room button clicked, but no song is currently selected.");
return;
}
ArenaRoomService roomService = ArenaRoomService.Instance;
if (roomService == null)
{
Debug.LogWarning("[SongDetailsUI] ArenaRoomService is missing.");
return;
}
if (roomService.IsInRoom)
{
try
{
await roomService.RefreshRoomInfo();
ShowRoomDetails(roomService);
}
catch (Exception ex)
{
Debug.LogWarning($"[SongDetailsUI] Failed to refresh current room: {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);
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);
}
return;
}
GameObject roomTemplate = ResolveRoomDetailsTemplate();
if (roomTemplate == null)
{
Debug.LogWarning("[SongDetailsUI] Room details prefab/template is missing.");
return;
}
spawnedRoomDetailsInstance = Instantiate(roomTemplate, ResolveRoomSpawnParent(), false);
PrepareSpawnedPanel(spawnedRoomDetailsInstance);
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)
{
Destroy(spawnedAskInstance);
spawnedAskInstance = null;
}
}
private void DestroyRoomDetailsInstance()
{
if (spawnedRoomDetailsInstance != null)
{
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;
}
}
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)
{
Button_EZ.image.color = new Color(1, 1, 1, selectedDifficulty == 0 ? 1 : 0);
Button_HD.image.color = new Color(1, 1, 1, selectedDifficulty == 1 ? 1 : 0);
Button_IN.image.color = new Color(1, 1, 1, selectedDifficulty == 2 ? 1 : 0);
Button_IM.image.color = new Color(1, 1, 1, selectedDifficulty == 3 ? 1 : 0);
}
private void OnDifficultySelected(int difficulty)
{
if (currentSong != null)
{
currentSong.thisLevel_selectedDifficultyID = difficulty;
SetDifficultyButtons(difficulty);
UpdateDifficultyUI(difficulty);
}
}
private void UpdateDifficultyUI(int difficulty)
{
var entry = GetChartEntry(difficulty);
if (entry != null)
{
difficultyText.text = entry.difficultyLEVEL.ToString();
difficultyDesciptionText.text = entry.difficultyDescription;
if (difficultyIdText != null)
difficultyIdText.text = entry.difficultyLEVEL.ToString();
if (progressImage != null)
progressImage.fillAmount = entry.levelProgressForThisDifficulty;
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
{
difficultyText.text = difficulty.ToString();
if (difficultyIdText != null)
difficultyIdText.text = difficulty.ToString();
if (progressImage != null)
progressImage.fillAmount = 0f;
UpdateProgressUI(0f);
difficultyDesciptionText.text = "";
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)
{
// 1. Update Text (can exceed 100%)
if (progressPercentText != null)
{
progressPercentText.text = Mathf.FloorToInt(progress * 100f).ToString() + "%";
}
// 2. Clamp for visual elements (fillAmount and Object position)
float clampedProgress = Mathf.Clamp01(progress);
// Update fillAmount again just in case, though handled in caller
if (progressImage != null)
{
progressImage.fillAmount = clampedProgress;
}
// 3. Update Object Position (Linear transformation: 0% -> -785, 100% -> -195)
if (progressNumberObject != null)
{
RectTransform rect = progressNumberObject.GetComponent<RectTransform>();
if (rect != null)
{
float targetX = Mathf.Lerp(-785f, -195f, clampedProgress);
Vector2 anchoredPos = rect.anchoredPosition;
anchoredPos.x = targetX;
rect.anchoredPosition = anchoredPos;
}
}
}
private ChartFileEntry GetChartEntry(int difficulty)
{
foreach (var entry in currentSong.chartFiles)
{
if (entry.difficulty == difficulty)
return entry;
}
return null;
}
}