using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; using UnityEngine.Serialization; using UnityEngine.SceneManagement; using System.Collections; using UnityEngine.Audio; using DG.Tweening; public class settlementController : MonoBehaviour { [Header("Inspector")] [SerializeField] private BeatmapManager bmm; [SerializeField] private ScoreManager sm; public GameManager gm; private SongData thisSong_so; private int maxScore_sum = 2000000; [Header("1000000")] public int _1000000 = 1000000; [Header("song image")] [SerializeField] private Image thisSong_backPic; [Header("Inspector")] public Button exit_toSelectSongs; public Button replay_thisGame; public Button display_rankList; public Button share_toSocialMedia; [Header("Text And Progress")] public Text songName_Text; [Tooltip("Documentation text normalized.")] public Text thisLevel_currentPercentage_Text; public Text finalScore_Text; public Image finalLevel_img; public Text pmScoreSum_Text; public Text idolScoreSum_Text; public Text accuracy_Text; public Text personalRecord_Text; public Image thisLevel_progressBar_Image; [Header("Rank Config")] public RankConfig rankConfig; [Header("Accuracy Weights")] public float perfect_weight = 1f; public float great_weight = 0.6666667f; public float good_weight = 0.333333f; public float miss_weight = 0; [Header("Inspector")] public Text reward_playerEXP_Text; public Text reward_money_Text; public Text reward_idolEXP_bottle_Text; [Header("Inspector")] public loadSettlementTeamPrefab settlementTeamLoader; public Image mvp_hero_hd_image; [Header("Inspector")] [SerializeField] private long moneyToGive_thisLevel; [Header("mvp")] public GameObject mvp_object; public Text mvp_score; public Image mvp_heroIcon; public Text mvp_heroName; // Documentation text normalized. [Header("Inspector")] public Text perfectHitCount_Text; public Text perfectHitPercent_Text; public Image perfect_barFill_Image; public Text greatHitCount_Text; public Text greatHitPercent_Text; public Image great_barFill_Image; public Text goodHitCount_Text; public Text goodHitPercent_Text; public Image good_barFill_Image; public Text missHitCount_Text; public Text missHitPercent_Text; public Image miss_barFill_Image; public Text gameNote_rate; [Header("Timing Statistics")] public Text earlyHitCount_Text; public Text lateHitCount_Text; public Text avgOffset_Text; [Header("Inspector")] [Tooltip("Settlement background music clip used during the settlement flow.")] public AudioSource settlementAudioSource; [Tooltip("Original gameplay music source (usually the chart song). ")] public AudioSource oriGameMusicSource; [Tooltip("Documentation text normalized.")] public AudioMixer gameMusicMixer; [Tooltip("Documentation text normalized.")] public string lowpassParamName = "inGameMusic_lowpass"; [Tooltip("Duration of lowpass transition in seconds.")] public float lowpassFadeDuration = 2f; [Tooltip("Settlement CanvasGroup used for fade-in.")] public CanvasGroup settlementCanvasGroup; [Header("Inspector")] [Tooltip("Settlement control manager object.")] public GameObject cdm; private Coroutine musicTransitionCoroutine; private Coroutine canvasFadeCoroutine; private Coroutine settlementIntroCoroutine; private bool settlementUiInitialized = false; [Header("Settlement Intro Animation")] [SerializeField] private bool enableDetailedSettlementIntro = true; [SerializeField] private int introFlashCount = 2; [SerializeField] private float introFlashDuration = 0.24f; [SerializeField] private float introMoveDuration = 0.42f; [SerializeField] private float introNumberDuration = 0.48f; [SerializeField] private float introStepGap = 0.06f; [SerializeField] private float introMvpEnterYOffset = 120f; [SerializeField] private float introMvpEnterDuration = 0.42f; [SerializeField] private Ease introMvpEnterEase = Ease.OutCubic; [SerializeField] private bool introTweenUseUnscaledTime = true; private int targetPmScore; private int targetIdolScore; private int targetTotalScore; private float targetAccuracyPercent; private float targetTotalPercent; private int targetPerfectCount; private int targetGreatCount; private int targetGoodCount; private int targetMissCount; private float targetPerfectPercent; private float targetGreatPercent; private float targetGoodPercent; private float targetMissPercent; private Tween mvpEntryTween; private bool skipIntroRequested; private Transform cachedIntroMvpTransform; private Vector3 cachedIntroMvpBaseLocalPos; private bool hasCachedIntroMvpBaseLocalPos; private void Awake() { // Documentation text normalized. if (replay_thisGame != null) { replay_thisGame.onClick.AddListener(OnReplayButtonClicked); } if (exit_toSelectSongs != null) { exit_toSelectSongs.onClick.AddListener(OnExitButtonClicked); } // Documentation text normalized. if (cdm != null) { cdm.SetActive(false); if (JudgeManager.IsDebugEnabled) Debug.Log("[SettlementController] CDM object disabled at startup"); } else { Debug.LogWarning("[SettlementController] CDM object is not assigned"); } } private void Start() { // Documentation text normalized. if (gameMusicMixer != null && !string.IsNullOrEmpty(lowpassParamName)) { try { gameMusicMixer.SetFloat(lowpassParamName, 22000f); } catch { } } } private void Update() { if (settlementIntroCoroutine == null) return; if (!enableDetailedSettlementIntro) return; if (Input.GetMouseButtonDown(0)) { SkipSettlementIntroAnimations(); } } private void OnDestroy() { if (replay_thisGame != null) { replay_thisGame.onClick.RemoveListener(OnReplayButtonClicked); } if (exit_toSelectSongs != null) { exit_toSelectSongs.onClick.RemoveListener(OnExitButtonClicked); } // Documentation text normalized. if (musicTransitionCoroutine != null) { StopCoroutine(musicTransitionCoroutine); musicTransitionCoroutine = null; } // Documentation text normalized. if (canvasFadeCoroutine != null) { StopCoroutine(canvasFadeCoroutine); canvasFadeCoroutine = null; } if (settlementIntroCoroutine != null) { StopCoroutine(settlementIntroCoroutine); settlementIntroCoroutine = null; } if (mvpEntryTween != null) { if (mvpEntryTween.active) mvpEntryTween.Kill(false); mvpEntryTween = null; } } // Start is called once before the first execution of Update after the MonoBehaviour is created public void getThisSong_info() { if (bmm != null) { thisSong_so = bmm.assignedSongData; } } public void startSettlement_uiUpdate() { if (settlementUiInitialized) { Debug.Log("[SettlementController] startSettlement_uiUpdate ignored: settlement already initialized."); return; } settlementUiInitialized = true; // Documentation text normalized. InitializeSettlementCanvas(); // Documentation text normalized. if (cdm != null) { cdm.SetActive(true); Debug.Log("[SettlementController] CDM object enabled at settlement start"); } getThisSong_info(); // --- Statistics: Record total play time at settlement --- if (gm != null) gm.RecordTotalPlayTime(); else { var activeGM = FindAnyObjectByType(); if (activeGM != null) activeGM.RecordTotalPlayTime(); } // Documentation text normalized. PrepareSettlementMusic(); if(sm != null) { songName_Text.text = bmm.parsedTitle; thisSong_backPic.sprite = bmm.assignedSongData.fullscreen_songPicture; targetPmScore = sm.allSum_pmScore; targetIdolScore = sm.allSum_idolScore; targetTotalScore = targetPmScore + targetIdolScore; targetTotalPercent = (float)targetTotalScore / Mathf.Max(1, _1000000) * 100f; finalScore_Text.text = targetTotalScore.ToString(); pmScoreSum_Text.text = targetPmScore.ToString(); // 根据总分从 rankConfig 获取等级图标并更新 finalLevel_img if (rankConfig != null && finalLevel_img != null) { finalLevel_img.sprite = rankConfig.GetRankSprite(targetTotalScore); Debug.Log($"[settlementController] 根据总分 {targetTotalScore} 更新等级图标"); } idolScoreSum_Text.text = targetIdolScore.ToString(); thisLevel_currentPercentage_Text.text = targetTotalPercent.ToString("F2") + "%"; thisLevel_progressBar_Image.fillAmount = (float)targetTotalScore / Mathf.Max(1, _1000000); int noteCountRaw = sm.countPerfect + sm.countGreat + sm.countGood + sm.countMiss; int noteCountSum = Mathf.Max(1, noteCountRaw); targetPerfectCount = sm.countPerfect; targetGreatCount = sm.countGreat; targetGoodCount = sm.countGood; targetMissCount = sm.countMiss; targetPerfectPercent = (float)targetPerfectCount / noteCountSum * 100f; targetGreatPercent = (float)targetGreatCount / noteCountSum * 100f; targetGoodPercent = (float)targetGoodCount / noteCountSum * 100f; targetMissPercent = (float)targetMissCount / noteCountSum * 100f; targetAccuracyPercent = ((float)(targetPerfectCount * perfect_weight + targetGreatCount * great_weight + targetGoodCount * good_weight + targetMissCount * miss_weight) / noteCountSum * 100f); perfectHitCount_Text.text = targetPerfectCount.ToString(); greatHitCount_Text.text = targetGreatCount.ToString(); goodHitCount_Text.text = targetGoodCount.ToString(); missHitCount_Text.text = targetMissCount.ToString(); perfect_barFill_Image.fillAmount = (float)targetPerfectCount / noteCountSum; great_barFill_Image.fillAmount = (float)targetGreatCount / noteCountSum; good_barFill_Image.fillAmount = (float)targetGoodCount / noteCountSum; miss_barFill_Image.fillAmount = (float)targetMissCount / noteCountSum; gameNote_rate.text = "已完成 " + noteCountRaw.ToString() + "/" + bmm.parsedNoteAmount.ToString(); perfectHitPercent_Text.text = targetPerfectPercent.ToString("F1") + "%"; greatHitPercent_Text.text = targetGreatPercent.ToString("F1") + "%"; goodHitPercent_Text.text = targetGoodPercent.ToString("F1") + "%"; missHitPercent_Text.text = targetMissPercent.ToString("F1") + "%"; accuracy_Text.text = targetAccuracyPercent.ToString("F3") + "%"; // Timing Statistics if (earlyHitCount_Text != null) earlyHitCount_Text.text = sm.countEarly.ToString(); if (lateHitCount_Text != null) lateHitCount_Text.text = sm.countLate.ToString(); if (avgOffset_Text != null) { float avg = sm.offsetCount > 0 ? sm.totalOffsetMs / sm.offsetCount : 0f; avgOffset_Text.text = avg.ToString("F2") + " ms"; } } else Debug.LogError("score manager is null"); // --- Achievement System: Load and display achievements --- if (InGamePerformanceManager.Instance != null && !GameConfig.autoPlayEnabled) { // Calculate total cure, damage and mana from teamUIController float totalCure = 0; float totalDamage = 0; float totalMana = 0; if (teamUIController.Instance != null) { if (teamUIController.Instance.totalHealProvided != null) { foreach (float heal in teamUIController.Instance.totalHealProvided) { totalCure += heal; } } if (teamUIController.Instance.totalDamageDealt != null) { foreach (float dmg in teamUIController.Instance.totalDamageDealt) { totalDamage += dmg; } } if (teamUIController.Instance.totalManaRestored != null) { foreach (float mana in teamUIController.Instance.totalManaRestored) { totalMana += mana; } } Debug.Log($"[SettlementController] Stats Aggregated - Cure: {totalCure}, Damage: {totalDamage}, Mana: {totalMana}"); } // Update achievement categories InGamePerformanceManager.Instance.UpdateTotalCure(totalCure); InGamePerformanceManager.Instance.UpdateTotalDamage(totalDamage); InGamePerformanceManager.Instance.UpdateTotalManaRestored(totalMana); if (sm != null) InGamePerformanceManager.Instance.UpdateTotalScore(sm.allSum_pmScore + sm.allSum_idolScore); // Load and display achievement prefabs InGamePerformanceManager.Instance.LoadArchievePrefab(); } // Persist run results into the SongData SO for this difficulty // (update personal / idol / total records and per-entry progress if improved) getThisSong_info(); // ensure thisSong_so set if (thisSong_so != null && bmm != null) { int oldPersonalRecord = thisSong_so.personalRecord; int diff = bmm.assignedDifficulty; if (diff >= 0 && !GameConfig.autoPlayEnabled) { int pm = sm != null ? sm.allSum_pmScore : 0; int idol = sm != null ? sm.allSum_idolScore : 0; thisSong_so.ApplySettlementResult(diff, pm, idol, _1000000); } if (personalRecord_Text != null) { int currentRecord = thisSong_so.personalRecord; personalRecord_Text.text = currentRecord.ToString(); // If current total score broke the overall personal record (all difficulties) // and we are NOT in autoplay (since we didn't save the result in autoplay) if (!GameConfig.autoPlayEnabled && targetTotalScore > oldPersonalRecord) { personalRecord_Text.color = new Color(0f, 0.392f, 0f); // Dark Green #006400 } } } // Populate settlement team cards if a loader is assigned if (settlementTeamLoader == null) { // try to auto-locate loader in scene if not assigned in inspector settlementTeamLoader = FindAnyObjectByType(); } if (settlementTeamLoader != null) { // ensure loader has key references if (settlementTeamLoader.sm == null) settlementTeamLoader.sm = sm ?? ScoreManager.Instance; if (settlementTeamLoader.tuic == null) settlementTeamLoader.tuic = teamUIController.Instance; if (settlementTeamLoader.gm == null) settlementTeamLoader.gm = gm ?? FindAnyObjectByType(); try { settlementTeamLoader.PopulateSettlementCards(); } catch (System.Exception ex) { Debug.LogWarning("Failed to PopulateSettlementCards: " + ex); } } // Documentation text normalized. // Documentation text normalized. SetMvpHeroImageFromTopScorer(); StartMusicTransition(); // Play settlement entry animation flow. StartSettlementIntro(); } private void StartSettlementIntro() { skipIntroRequested = false; if (!enableDetailedSettlementIntro) { if (settlementTeamLoader != null) settlementTeamLoader.ShowSpawnedCardsInstantly(); StartCanvasFadeIn(); return; } if (settlementIntroCoroutine != null) { StopCoroutine(settlementIntroCoroutine); settlementIntroCoroutine = null; } settlementIntroCoroutine = StartCoroutine(SettlementIntroRoutine()); } private IEnumerator SettlementIntroRoutine() { if (settlementCanvasGroup == null) { StartCanvasFadeIn(); yield break; } settlementCanvasGroup.alpha = 0f; settlementCanvasGroup.interactable = false; settlementCanvasGroup.blocksRaycasts = false; Transform settleRoot = settlementCanvasGroup.transform; Vector3 settleBaseScale = settleRoot != null ? settleRoot.localScale : Vector3.one; Transform rightRoot = FindDescendantByName(settleRoot, "right"); Transform leftRoot = FindDescendantByName(settleRoot, "left"); Transform picsRoot = FindDescendantByName(leftRoot != null ? leftRoot : settleRoot, "pics"); RectTransform rightBottomRt = FindRectByName(rightRoot != null ? rightRoot : settleRoot, "right_bottom"); Transform quhuiImageTr = FindDescendantByName(picsRoot != null ? picsRoot : settleRoot, "quhuiImage"); RectTransform scoreBottomRt = FindRectByName(settleRoot, "score_bottom"); RectTransform shenstarsRt = FindRectByName(settleRoot, "shenstars"); RectTransform rewardRt = FindRectByName(settleRoot, "reward"); Transform heroDApicTr = FindDescendantByName(rightRoot != null ? rightRoot : settleRoot, "heroDApic"); Transform heroMaskTr = FindDescendantByName(heroDApicTr != null ? heroDApicTr : settleRoot, "MASK"); Transform lihuiTr = FindDescendantByName(heroMaskTr != null ? heroMaskTr : settleRoot, "lihui"); Transform teamDisplayingTr = FindDescendantByName(rightRoot != null ? rightRoot : settleRoot, "teamDisplaying"); RectTransform teamDisplayingRt = teamDisplayingTr as RectTransform; Transform mvpTr = (mvp_object != null) ? mvp_object.transform : FindDescendantByName(heroDApicTr != null ? heroDApicTr : settleRoot, "mvp"); bool hasAllyCardEntry = settlementTeamLoader != null && settlementTeamLoader.HasSpawnedCards(); bool shouldAnimateMvp = mvpTr != null && mvpTr.gameObject.activeInHierarchy; Vector3 mvpBaseLocalPos = shouldAnimateMvp ? mvpTr.localPosition : Vector3.zero; cachedIntroMvpTransform = mvpTr; hasCachedIntroMvpBaseLocalPos = shouldAnimateMvp; if (shouldAnimateMvp) cachedIntroMvpBaseLocalPos = mvpBaseLocalPos; Transform buttonsRoot = FindDescendantByName(rightRoot != null ? rightRoot : settleRoot, "buttons"); List rightButtons = CollectDirectChildren(buttonsRoot); Transform hitStatusTr = FindDescendantByName(settleRoot, "hitStatus"); Transform statusTr = FindDescendantByName(hitStatusTr != null ? hitStatusTr : settleRoot, "status"); List statusGroups = CollectDirectChildren(statusTr); Transform perfectGroup = FindGroupRoot(perfectHitCount_Text, perfectHitPercent_Text); Transform greatGroup = FindGroupRoot(greatHitCount_Text, greatHitPercent_Text); Transform goodGroup = FindGroupRoot(goodHitCount_Text, goodHitPercent_Text); Transform missGroup = FindGroupRoot(missHitCount_Text, missHitPercent_Text); List orderedGroups = new List(); if (perfectGroup != null) orderedGroups.Add(perfectGroup); if (greatGroup != null) orderedGroups.Add(greatGroup); if (goodGroup != null) orderedGroups.Add(goodGroup); if (missGroup != null) orderedGroups.Add(missGroup); if (orderedGroups.Count == 0) orderedGroups = statusGroups; if (perfectGroup == null && orderedGroups.Count > 0) perfectGroup = orderedGroups[0]; if (greatGroup == null && orderedGroups.Count > 1) greatGroup = orderedGroups[1]; if (goodGroup == null && orderedGroups.Count > 2) goodGroup = orderedGroups[2]; if (missGroup == null && orderedGroups.Count > 3) missGroup = orderedGroups[3]; // Initial hidden/setup state before entrance. if (rightBottomRt != null) SetAnchoredX(rightBottomRt, 1799f); if (quhuiImageTr != null) SetCanvasAlpha(quhuiImageTr, 0f); if (scoreBottomRt != null) { SetAnchoredX(scoreBottomRt, -1646.9f); SetCanvasAlpha(scoreBottomRt, 0f); } if (shenstarsRt != null) { SetAnchoredX(shenstarsRt, -1602.1f); SetCanvasAlpha(shenstarsRt, 0f); } if (rewardRt != null) { SetAnchoredY(rewardRt, -494.3f); SetCanvasAlpha(rewardRt, 0f); } if (lihuiTr != null) SetCanvasAlpha(lihuiTr, 0f); if (teamDisplayingRt != null) { if (hasAllyCardEntry) { SetAnchoredX(teamDisplayingRt, 1490.09f); SetCanvasAlpha(teamDisplayingRt, 1f); } else { SetAnchoredX(teamDisplayingRt, 2265f); SetCanvasAlpha(teamDisplayingRt, 0f); } } if (shouldAnimateMvp) { SetCanvasAlpha(mvpTr, 0f); mvpTr.localPosition = new Vector3(mvpBaseLocalPos.x, mvpBaseLocalPos.y + introMvpEnterYOffset, mvpBaseLocalPos.z); } for (int i = 0; i < rightButtons.Count; i++) { if (rightButtons[i] != null) SetCanvasAlpha(rightButtons[i], 0f); } for (int i = 0; i < orderedGroups.Count; i++) { if (orderedGroups[i] != null) SetCanvasAlpha(orderedGroups[i], 0f); } PrepareTextForCountUp(pmScoreSum_Text, false, 0); PrepareTextForCountUp(idolScoreSum_Text, false, 0); PrepareTextForCountUp(accuracy_Text, true, 0f, "F3"); PrepareTextForCountUp(finalScore_Text, false, 0); PrepareTextForCountUp(thisLevel_currentPercentage_Text, true, 0f, "F2"); PrepareTextForCountUp(perfectHitCount_Text, false, 0); PrepareTextForCountUp(greatHitCount_Text, false, 0); PrepareTextForCountUp(goodHitCount_Text, false, 0); PrepareTextForCountUp(missHitCount_Text, false, 0); PrepareTextForCountUp(perfectHitPercent_Text, true, 0f, "F1"); PrepareTextForCountUp(greatHitPercent_Text, true, 0f, "F1"); PrepareTextForCountUp(goodHitPercent_Text, true, 0f, "F1"); PrepareTextForCountUp(missHitPercent_Text, true, 0f, "F1"); // settlement: flash + scale 1.2 -> 1.0 yield return StartCoroutine(FlashCanvasGroupWithScale(settlementCanvasGroup, settleRoot, settleBaseScale * 1.2f, settleBaseScale, introFlashCount, introFlashDuration)); // right/right_bottom: X 1799 -> 1677.75 if (rightBottomRt != null) yield return StartCoroutine(AnimateAnchoredXAndFade(rightBottomRt, 1799f, 1677.75f, introMoveDuration, false)); // left/pics/quhuiImage flash if (quhuiImageTr != null) yield return StartCoroutine(FlashInTransform(quhuiImageTr, introFlashCount, introFlashDuration)); // score_bottom + shenstars move in together Coroutine scoreBottomIn = null; Coroutine shenstarsIn = null; if (scoreBottomRt != null) scoreBottomIn = StartCoroutine(AnimateAnchoredXAndFade(scoreBottomRt, -1646.9f, -432.4f, introMoveDuration, true)); if (shenstarsRt != null) shenstarsIn = StartCoroutine(AnimateAnchoredXAndFade(shenstarsRt, -1602.1f, -437.5785f, introMoveDuration, true)); if (scoreBottomIn != null || shenstarsIn != null) yield return WaitRealtime(introMoveDuration + 0.02f); // reward: Y -494.3 -> 0 with fade if (rewardRt != null) yield return StartCoroutine(AnimateAnchoredYAndFade(rewardRt, -494.3f, 0f, introMoveDuration)); // score numbers: pm -> idol -> accuracy yield return StartCoroutine(AnimateIntText(pmScoreSum_Text, targetPmScore, introNumberDuration)); yield return StartCoroutine(AnimateIntText(idolScoreSum_Text, targetIdolScore, introNumberDuration)); yield return StartCoroutine(AnimateFloatPercentText(accuracy_Text, targetAccuracyPercent, introNumberDuration, "F3")); // total + percent after above, and flash once completed Coroutine totalScoreIn = StartCoroutine(AnimateIntText(finalScore_Text, targetTotalScore, introNumberDuration)); Coroutine totalPercentIn = StartCoroutine(AnimateFloatPercentText(thisLevel_currentPercentage_Text, targetTotalPercent, introNumberDuration, "F2")); if (totalScoreIn != null || totalPercentIn != null) yield return WaitRealtime(introNumberDuration + 0.02f); yield return StartCoroutine(FlashInTransform(finalScore_Text != null ? finalScore_Text.transform : null, introFlashCount, introFlashDuration * 0.9f)); yield return StartCoroutine(FlashInTransform(thisLevel_currentPercentage_Text != null ? thisLevel_currentPercentage_Text.transform : null, introFlashCount, introFlashDuration * 0.9f)); // hitStatus/status groups: flash in and count up numbers yield return StartCoroutine(PlayStatusGroupIn(perfectGroup, perfectHitCount_Text, targetPerfectCount, perfectHitPercent_Text, targetPerfectPercent)); yield return StartCoroutine(PlayStatusGroupIn(greatGroup, greatHitCount_Text, targetGreatCount, greatHitPercent_Text, targetGreatPercent)); yield return StartCoroutine(PlayStatusGroupIn(goodGroup, goodHitCount_Text, targetGoodCount, goodHitPercent_Text, targetGoodPercent)); yield return StartCoroutine(PlayStatusGroupIn(missGroup, missHitCount_Text, targetMissCount, missHitPercent_Text, targetMissPercent)); // ally cards enter before MASK. if (hasAllyCardEntry) yield return StartCoroutine(settlementTeamLoader.PlayAllySlotEntryBeforeMask()); // heroDApic/MASK/lihui flash if (lihuiTr != null) yield return StartCoroutine(FlashInTransform(lihuiTr, introFlashCount, introFlashDuration)); // MVP shows after MASK. if (shouldAnimateMvp) yield return StartCoroutine(AnimateMvpEntryAfterMask(mvpTr, mvpBaseLocalPos)); // Fallback for scenes still using container slide-in instead of per-slot tween. if (!hasAllyCardEntry && teamDisplayingRt != null) yield return StartCoroutine(AnimateAnchoredXAndFade(teamDisplayingRt, 2265f, 1490.09f, introMoveDuration, true)); // right/buttons: each button flashes in sequence for (int i = 0; i < rightButtons.Count; i++) { Transform btn = rightButtons[i]; if (btn == null) continue; yield return StartCoroutine(FlashInTransform(btn, introFlashCount, introFlashDuration)); yield return WaitRealtime(introStepGap); } settlementCanvasGroup.alpha = 1f; settlementCanvasGroup.interactable = true; settlementCanvasGroup.blocksRaycasts = true; hasCachedIntroMvpBaseLocalPos = false; cachedIntroMvpTransform = null; settlementIntroCoroutine = null; } private void SkipSettlementIntroAnimations() { if (skipIntroRequested) return; skipIntroRequested = true; if (settlementIntroCoroutine != null) { StopCoroutine(settlementIntroCoroutine); settlementIntroCoroutine = null; } if (mvpEntryTween != null) { if (mvpEntryTween.active) mvpEntryTween.Kill(false); mvpEntryTween = null; } if (settlementTeamLoader != null) settlementTeamLoader.CompleteAllySlotEntryImmediately(); ApplySettlementIntroFinalState(); if (settlementCanvasGroup != null) { settlementCanvasGroup.alpha = 1f; settlementCanvasGroup.interactable = true; settlementCanvasGroup.blocksRaycasts = true; } hasCachedIntroMvpBaseLocalPos = false; cachedIntroMvpTransform = null; } private void ApplySettlementIntroFinalState() { Transform settleRoot = settlementCanvasGroup != null ? settlementCanvasGroup.transform : transform; Transform rightRoot = FindDescendantByName(settleRoot, "right"); Transform leftRoot = FindDescendantByName(settleRoot, "left"); Transform picsRoot = FindDescendantByName(leftRoot != null ? leftRoot : settleRoot, "pics"); RectTransform rightBottomRt = FindRectByName(rightRoot != null ? rightRoot : settleRoot, "right_bottom"); Transform quhuiImageTr = FindDescendantByName(picsRoot != null ? picsRoot : settleRoot, "quhuiImage"); RectTransform scoreBottomRt = FindRectByName(settleRoot, "score_bottom"); RectTransform shenstarsRt = FindRectByName(settleRoot, "shenstars"); RectTransform rewardRt = FindRectByName(settleRoot, "reward"); RectTransform teamDisplayingRt = FindRectByName(rightRoot != null ? rightRoot : settleRoot, "teamDisplaying"); Transform buttonsRoot = FindDescendantByName(rightRoot != null ? rightRoot : settleRoot, "buttons"); List rightButtons = CollectDirectChildren(buttonsRoot); Transform heroDApicTr = FindDescendantByName(rightRoot != null ? rightRoot : settleRoot, "heroDApic"); Transform heroMaskTr = FindDescendantByName(heroDApicTr != null ? heroDApicTr : settleRoot, "MASK"); Transform lihuiTr = FindDescendantByName(heroMaskTr != null ? heroMaskTr : settleRoot, "lihui"); Transform mvpTr = (mvp_object != null) ? mvp_object.transform : FindDescendantByName(heroDApicTr != null ? heroDApicTr : settleRoot, "mvp"); if (rightBottomRt != null) { SetAnchoredX(rightBottomRt, 1677.75f); SetCanvasAlpha(rightBottomRt, 1f); } if (quhuiImageTr != null) SetCanvasAlpha(quhuiImageTr, 1f); if (scoreBottomRt != null) { SetAnchoredX(scoreBottomRt, -432.4f); SetCanvasAlpha(scoreBottomRt, 1f); } if (shenstarsRt != null) { SetAnchoredX(shenstarsRt, -437.5785f); SetCanvasAlpha(shenstarsRt, 1f); } if (rewardRt != null) { SetAnchoredY(rewardRt, 0f); SetCanvasAlpha(rewardRt, 1f); } if (lihuiTr != null) SetCanvasAlpha(lihuiTr, 1f); if (teamDisplayingRt != null) { SetAnchoredX(teamDisplayingRt, 1490.09f); SetCanvasAlpha(teamDisplayingRt, 1f); } for (int i = 0; i < rightButtons.Count; i++) { if (rightButtons[i] != null) SetCanvasAlpha(rightButtons[i], 1f); } Transform perfectGroup = FindGroupRoot(perfectHitCount_Text, perfectHitPercent_Text); Transform greatGroup = FindGroupRoot(greatHitCount_Text, greatHitPercent_Text); Transform goodGroup = FindGroupRoot(goodHitCount_Text, goodHitPercent_Text); Transform missGroup = FindGroupRoot(missHitCount_Text, missHitPercent_Text); if (perfectGroup != null) SetCanvasAlpha(perfectGroup, 1f); if (greatGroup != null) SetCanvasAlpha(greatGroup, 1f); if (goodGroup != null) SetCanvasAlpha(goodGroup, 1f); if (missGroup != null) SetCanvasAlpha(missGroup, 1f); int noteCountSum = Mathf.Max(1, targetPerfectCount + targetGreatCount + targetGoodCount + targetMissCount); if (pmScoreSum_Text != null) { pmScoreSum_Text.text = targetPmScore.ToString(); SetCanvasAlpha(pmScoreSum_Text.transform, 1f); } if (idolScoreSum_Text != null) { idolScoreSum_Text.text = targetIdolScore.ToString(); SetCanvasAlpha(idolScoreSum_Text.transform, 1f); } if (accuracy_Text != null) { accuracy_Text.text = targetAccuracyPercent.ToString("F3") + "%"; SetCanvasAlpha(accuracy_Text.transform, 1f); } if (finalScore_Text != null) { finalScore_Text.text = targetTotalScore.ToString(); SetCanvasAlpha(finalScore_Text.transform, 1f); } if (thisLevel_currentPercentage_Text != null) { thisLevel_currentPercentage_Text.text = targetTotalPercent.ToString("F2") + "%"; SetCanvasAlpha(thisLevel_currentPercentage_Text.transform, 1f); } if (perfectHitCount_Text != null) { perfectHitCount_Text.text = targetPerfectCount.ToString(); SetCanvasAlpha(perfectHitCount_Text.transform, 1f); } if (greatHitCount_Text != null) { greatHitCount_Text.text = targetGreatCount.ToString(); SetCanvasAlpha(greatHitCount_Text.transform, 1f); } if (goodHitCount_Text != null) { goodHitCount_Text.text = targetGoodCount.ToString(); SetCanvasAlpha(goodHitCount_Text.transform, 1f); } if (missHitCount_Text != null) { missHitCount_Text.text = targetMissCount.ToString(); SetCanvasAlpha(missHitCount_Text.transform, 1f); } if (perfectHitPercent_Text != null) { perfectHitPercent_Text.text = targetPerfectPercent.ToString("F1") + "%"; SetCanvasAlpha(perfectHitPercent_Text.transform, 1f); } if (greatHitPercent_Text != null) { greatHitPercent_Text.text = targetGreatPercent.ToString("F1") + "%"; SetCanvasAlpha(greatHitPercent_Text.transform, 1f); } if (goodHitPercent_Text != null) { goodHitPercent_Text.text = targetGoodPercent.ToString("F1") + "%"; SetCanvasAlpha(goodHitPercent_Text.transform, 1f); } if (missHitPercent_Text != null) { missHitPercent_Text.text = targetMissPercent.ToString("F1") + "%"; SetCanvasAlpha(missHitPercent_Text.transform, 1f); } if (thisLevel_progressBar_Image != null) thisLevel_progressBar_Image.fillAmount = (float)targetTotalScore / Mathf.Max(1, _1000000); if (perfect_barFill_Image != null) perfect_barFill_Image.fillAmount = (float)targetPerfectCount / noteCountSum; if (great_barFill_Image != null) great_barFill_Image.fillAmount = (float)targetGreatCount / noteCountSum; if (good_barFill_Image != null) good_barFill_Image.fillAmount = (float)targetGoodCount / noteCountSum; if (miss_barFill_Image != null) miss_barFill_Image.fillAmount = (float)targetMissCount / noteCountSum; if (mvpTr != null && mvpTr.gameObject.activeInHierarchy) { Vector3 targetPos; if (hasCachedIntroMvpBaseLocalPos && cachedIntroMvpTransform == mvpTr) targetPos = cachedIntroMvpBaseLocalPos; else targetPos = new Vector3(mvpTr.localPosition.x, mvpTr.localPosition.y - introMvpEnterYOffset, mvpTr.localPosition.z); mvpTr.localPosition = targetPos; SetCanvasAlpha(mvpTr, 1f); } } /// /// Documentation text normalized. /// private void InitializeSettlementCanvas() { if (settlementCanvasGroup != null) { settlementCanvasGroup.alpha = 0f; settlementCanvasGroup.interactable = false; settlementCanvasGroup.blocksRaycasts = false; Debug.Log("[SettlementController] Settlement canvas initialized to transparent state"); } else { Debug.LogWarning("[SettlementController] settlementCanvasGroup is not assigned, cannot initialize"); } } // Documentation text normalized. private static AllyHero_SO[] _cachedAllyHeroSOs; /// /// Documentation text normalized. /// private void PrepareSettlementMusic() { if (thisSong_so == null) { Debug.LogWarning("[SettlementController] thisSong_so is null, cannot prepare settlement music"); return; } if (settlementAudioSource == null) { Debug.LogWarning("[SettlementController] settlementAudioSource is not assigned, cannot prepare settlement music"); return; } dlcData[] allDlcs = Resources.LoadAll(""); dlcData foundDlc = null; for (int i = 0; i < allDlcs.Length; i++) { dlcData dlc = allDlcs[i]; if (dlc == null || dlc.songList == null) continue; if (!dlc.songList.Contains(thisSong_so)) continue; foundDlc = dlc; break; } if (foundDlc == null) { Debug.LogWarning($"[SettlementController] No DLC found containing song '{thisSong_so.songName}'"); return; } if (foundDlc.settlementMusic == null) { Debug.LogWarning($"[SettlementController] DLC '{foundDlc.dlcName}' does not have settlement music assigned"); return; } bool clipChanged = settlementAudioSource.clip != foundDlc.settlementMusic; if (clipChanged) { settlementAudioSource.Stop(); settlementAudioSource.clip = foundDlc.settlementMusic; settlementAudioSource.time = 0f; } settlementAudioSource.loop = true; settlementAudioSource.playOnAwake = false; settlementAudioSource.volume = 1f; settlementAudioSource.mute = false; Debug.Log($"[SettlementController] Prepared settlement music from DLC '{foundDlc.dlcName}': {foundDlc.settlementMusic.name}"); } /// /// Documentation text normalized. /// private void StartMusicTransition() { if (musicTransitionCoroutine != null) { StopCoroutine(musicTransitionCoroutine); } musicTransitionCoroutine = StartCoroutine(MusicTransitionRoutine()); } /// /// Documentation text normalized. /// Documentation text normalized. /// Documentation text normalized. /// private IEnumerator MusicTransitionRoutine() { float elapsed = 0f; float transitionDuration = Mathf.Max(0.01f, lowpassFadeDuration); float startVolume = (oriGameMusicSource != null) ? oriGameMusicSource.volume : 1f; bool settlementMusicStarted = false; float settlementStartVolume = 0f; // Start settlement music immediately to avoid delayed playback at settlement start. if (settlementAudioSource != null && settlementAudioSource.clip != null) { try { settlementAudioSource.mute = false; if (!settlementAudioSource.isPlaying) { settlementAudioSource.volume = 0f; settlementAudioSource.Play(); settlementStartVolume = 0f; } else { settlementStartVolume = Mathf.Clamp01(settlementAudioSource.volume); } settlementMusicStarted = true; Debug.Log($"[SettlementController] Settlement music active: {settlementAudioSource.clip.name}"); } catch (System.Exception ex) { Debug.LogWarning($"[SettlementController] Failed to start settlement music immediately: {ex.Message}"); } } if (oriGameMusicSource != null) { Debug.Log($"[SettlementController] Starting game-music fade out over {transitionDuration}s"); while (elapsed < transitionDuration) { elapsed += Time.unscaledDeltaTime; float t = Mathf.Clamp01(elapsed / transitionDuration); oriGameMusicSource.volume = Mathf.Lerp(startVolume, 0f, t); if (settlementMusicStarted && settlementAudioSource != null) settlementAudioSource.volume = Mathf.Lerp(settlementStartVolume, 1f, t); yield return null; } oriGameMusicSource.volume = 0f; oriGameMusicSource.Pause(); Debug.Log("[SettlementController] Game music fade complete, game music paused."); } else { Debug.LogWarning("[SettlementController] oriGameMusicSource not assigned, skipping game music fade"); if (settlementMusicStarted && settlementAudioSource != null) settlementAudioSource.volume = 1f; } if (!settlementMusicStarted && settlementAudioSource != null && settlementAudioSource.clip != null) { try { settlementAudioSource.mute = false; settlementAudioSource.volume = 1f; settlementAudioSource.Play(); Debug.Log($"[SettlementController] Settlement music started: {settlementAudioSource.clip.name}"); } catch (System.Exception ex) { Debug.LogWarning($"[SettlementController] Failed to start settlement music: {ex.Message}"); } } musicTransitionCoroutine = null; } /// /// Documentation text normalized. /// private void StartCanvasFadeIn() { if (canvasFadeCoroutine != null) { StopCoroutine(canvasFadeCoroutine); } canvasFadeCoroutine = StartCoroutine(CanvasFadeInRoutine()); } /// /// Documentation text normalized. /// private IEnumerator CanvasFadeInRoutine() { if (settlementCanvasGroup == null) { Debug.LogWarning("[SettlementController] settlementCanvasGroup is not assigned, cannot fade in"); yield break; } float fadeDuration = 0.25f; float elapsed = 0f; float startAlpha = settlementCanvasGroup.alpha; float targetAlpha = 1f; Debug.Log($"[SettlementController] Starting CanvasGroup fade-in from {startAlpha} to {targetAlpha} over {fadeDuration}s"); while (elapsed < fadeDuration) { elapsed += Time.unscaledDeltaTime; float t = Mathf.Clamp01(elapsed / fadeDuration); settlementCanvasGroup.alpha = Mathf.Lerp(startAlpha, targetAlpha, t); yield return null; } // Documentation text normalized. settlementCanvasGroup.alpha = targetAlpha; settlementCanvasGroup.interactable = true; settlementCanvasGroup.blocksRaycasts = true; Debug.Log("[SettlementController] CanvasGroup fade-in complete"); canvasFadeCoroutine = null; } private IEnumerator PlayStatusGroupIn(Transform groupRoot, Text countText, int countTarget, Text percentText, float percentTarget) { if (groupRoot != null) yield return StartCoroutine(FlashInTransform(groupRoot, introFlashCount, introFlashDuration)); Coroutine c1 = null; Coroutine c2 = null; if (countText != null) c1 = StartCoroutine(AnimateIntText(countText, countTarget, introNumberDuration)); if (percentText != null) c2 = StartCoroutine(AnimateFloatPercentText(percentText, percentTarget, introNumberDuration, "F1")); if (c1 != null || c2 != null) yield return WaitRealtime(introNumberDuration + 0.02f); } private IEnumerator AnimateMvpEntryAfterMask(Transform mvpTransform, Vector3 baseLocalPosition) { if (mvpTransform == null || !mvpTransform.gameObject.activeInHierarchy) yield break; if (mvpEntryTween != null) { if (mvpEntryTween.active) mvpEntryTween.Kill(false); mvpEntryTween = null; } CanvasGroup group = GetOrAddCanvasGroup(mvpTransform); if (group != null) group.alpha = 0f; mvpTransform.localPosition = new Vector3(baseLocalPosition.x, baseLocalPosition.y + introMvpEnterYOffset, baseLocalPosition.z); float duration = Mathf.Max(0.01f, introMvpEnterDuration); Sequence seq = DOTween.Sequence().SetUpdate(introTweenUseUnscaledTime); seq.Join(mvpTransform.DOLocalMoveY(baseLocalPosition.y, duration).SetEase(introMvpEnterEase).SetUpdate(introTweenUseUnscaledTime)); if (group != null) seq.Join(group.DOFade(1f, duration).SetEase(introMvpEnterEase).SetUpdate(introTweenUseUnscaledTime)); mvpEntryTween = seq; yield return seq.WaitForCompletion(); mvpEntryTween = null; } private IEnumerator AnimateIntText(Text target, int toValue, float duration) { if (target == null) yield break; SetCanvasAlpha(target.transform, 1f); float elapsed = 0f; float dur = Mathf.Max(0.01f, duration); while (elapsed < dur) { if (skipIntroRequested) break; elapsed += Time.unscaledDeltaTime; float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur)); int value = Mathf.RoundToInt(Mathf.Lerp(0f, toValue, t)); target.text = value.ToString(); yield return null; } target.text = toValue.ToString(); } private IEnumerator AnimateFloatPercentText(Text target, float toValue, float duration, string format) { if (target == null) yield break; SetCanvasAlpha(target.transform, 1f); float elapsed = 0f; float dur = Mathf.Max(0.01f, duration); while (elapsed < dur) { if (skipIntroRequested) break; elapsed += Time.unscaledDeltaTime; float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur)); float value = Mathf.Lerp(0f, toValue, t); target.text = value.ToString(format) + "%"; yield return null; } target.text = toValue.ToString(format) + "%"; } private IEnumerator AnimateAnchoredXAndFade(RectTransform rt, float fromX, float toX, float duration, bool fadeIn) { if (rt == null) yield break; SetAnchoredX(rt, fromX); if (fadeIn) SetCanvasAlpha(rt, 0f); float elapsed = 0f; float dur = Mathf.Max(0.01f, duration); while (elapsed < dur) { if (skipIntroRequested) break; elapsed += Time.unscaledDeltaTime; float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur)); SetAnchoredX(rt, Mathf.LerpUnclamped(fromX, toX, t)); if (fadeIn) SetCanvasAlpha(rt, t); yield return null; } SetAnchoredX(rt, toX); if (fadeIn) SetCanvasAlpha(rt, 1f); } private IEnumerator AnimateAnchoredYAndFade(RectTransform rt, float fromY, float toY, float duration) { if (rt == null) yield break; SetAnchoredY(rt, fromY); SetCanvasAlpha(rt, 0f); float elapsed = 0f; float dur = Mathf.Max(0.01f, duration); while (elapsed < dur) { if (skipIntroRequested) break; elapsed += Time.unscaledDeltaTime; float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur)); SetAnchoredY(rt, Mathf.LerpUnclamped(fromY, toY, t)); SetCanvasAlpha(rt, t); yield return null; } SetAnchoredY(rt, toY); SetCanvasAlpha(rt, 1f); } private IEnumerator FlashCanvasGroupWithScale(CanvasGroup cg, Transform scaleTarget, Vector3 fromScale, Vector3 toScale, int flashes, float duration) { if (cg == null) yield break; int count = Mathf.Max(1, flashes); float total = Mathf.Max(0.05f, duration); float flashWindow = total / Mathf.Max(1, count * 2); cg.alpha = 0f; if (scaleTarget != null) scaleTarget.localScale = fromScale; float elapsed = 0f; while (elapsed < total) { if (skipIntroRequested) break; elapsed += Time.unscaledDeltaTime; float p = Mathf.Clamp01(elapsed / total); if (scaleTarget != null) { float k = EaseOutCubic(p); scaleTarget.localScale = Vector3.LerpUnclamped(fromScale, toScale, k); } if (flashWindow > 0f) { int seg = Mathf.FloorToInt(elapsed / flashWindow); bool on = (seg % 2) == 0; cg.alpha = on ? 1f : 0f; } else { cg.alpha = 1f; } yield return null; } cg.alpha = 1f; if (scaleTarget != null) scaleTarget.localScale = toScale; } private IEnumerator FlashInTransform(Transform target, int flashes, float duration) { if (target == null) yield break; int count = Mathf.Max(1, flashes); float total = Mathf.Max(0.05f, duration); float step = total / Mathf.Max(1, count * 2); SetCanvasAlpha(target, 0f); for (int i = 0; i < count; i++) { SetCanvasAlpha(target, 1f); yield return WaitRealtime(step); if (i < count - 1) { SetCanvasAlpha(target, 0f); yield return WaitRealtime(step); } } SetCanvasAlpha(target, 1f); } private void PrepareTextForCountUp(Text t, bool isPercent, float initialValue, string format = "F1") { if (t == null) return; SetCanvasAlpha(t.transform, 0f); t.text = isPercent ? initialValue.ToString(format) + "%" : Mathf.RoundToInt(initialValue).ToString(); } private void PrepareTextForCountUp(Text t, bool isPercent, int initialValue) { if (t == null) return; SetCanvasAlpha(t.transform, 0f); t.text = isPercent ? initialValue.ToString("F1") + "%" : initialValue.ToString(); } private static float EaseOutCubic(float t) { float inv = 1f - t; return 1f - inv * inv * inv; } private IEnumerator WaitRealtime(float duration) { float elapsed = 0f; float dur = Mathf.Max(0f, duration); while (elapsed < dur) { if (skipIntroRequested) yield break; elapsed += Time.unscaledDeltaTime; yield return null; } } private static void SetAnchoredX(RectTransform rt, float x) { if (rt == null) return; Vector2 p = rt.anchoredPosition; p.x = x; rt.anchoredPosition = p; } private static void SetAnchoredY(RectTransform rt, float y) { if (rt == null) return; Vector2 p = rt.anchoredPosition; p.y = y; rt.anchoredPosition = p; } private static List CollectDirectChildren(Transform root) { List list = new List(); if (root == null) return list; for (int i = 0; i < root.childCount; i++) { Transform c = root.GetChild(i); if (c != null) list.Add(c); } return list; } private static Transform FindDescendantByName(Transform root, string exactName) { if (root == null || string.IsNullOrEmpty(exactName)) return null; Transform[] all = root.GetComponentsInChildren(true); for (int i = 0; i < all.Length; i++) { Transform t = all[i]; if (t == null) continue; if (string.Equals(t.name, exactName, System.StringComparison.OrdinalIgnoreCase)) return t; } return null; } private static RectTransform FindRectByName(Transform root, string exactName) { Transform t = FindDescendantByName(root, exactName); return t as RectTransform; } private static Transform FindGroupRoot(Text countText, Text percentText) { Transform c = countText != null ? countText.transform : null; Transform p = percentText != null ? percentText.transform : null; if (c == null && p == null) return null; if (c == null) return p != null ? p.parent : null; if (p == null) return c.parent; Transform probe = c; while (probe != null) { if (p.IsChildOf(probe)) return probe; probe = probe.parent; } return c.parent; } private static CanvasGroup GetOrAddCanvasGroup(Transform root) { if (root == null) return null; CanvasGroup cg = root.GetComponent(); if (cg == null) cg = root.gameObject.AddComponent(); return cg; } private static void SetCanvasAlpha(Transform root, float alpha) { if (root == null) return; CanvasGroup cg = GetOrAddCanvasGroup(root); if (cg == null) return; cg.alpha = Mathf.Clamp01(alpha); } /// /// Documentation text normalized. /// private void OnReplayButtonClicked() { Debug.Log("[SettlementController] Replay button clicked - restarting current game"); // Documentation text normalized. if (bmm == null) { Debug.LogError("[SettlementController] BeatmapManager is null, cannot replay"); return; } if (bmm.assignedSongData == null) { Debug.LogError("[SettlementController] AssignedSongData is null, cannot replay"); return; } // Documentation text normalized. SongData songToReplay = bmm.assignedSongData; int difficultyToReplay = bmm.assignedDifficulty; if (difficultyToReplay < 0) { Debug.LogError("[SettlementController] AssignedDifficulty is invalid, cannot replay"); return; } Debug.Log($"[SettlementController] Reloading song: {songToReplay.songName}, difficulty: {difficultyToReplay}"); // Documentation text normalized. if (settlementAudioSource != null && settlementAudioSource.isPlaying) { settlementAudioSource.Stop(); } // Documentation text normalized. BeatmapManager.SetPendingSong(songToReplay, difficultyToReplay); // Documentation text normalized. StartCoroutine(LoadSceneAsync(SceneManager.GetActiveScene().name)); } private IEnumerator LoadSceneAsync(string sceneName) { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); while (!asyncLoad.isDone) { yield return null; } } /// /// Documentation text normalized. /// private void OnExitButtonClicked() { Debug.Log("[SettlementController] Exit button clicked - returning to song selection"); // Documentation text normalized. if (settlementAudioSource != null && settlementAudioSource.isPlaying) { settlementAudioSource.Stop(); } // Documentation text normalized. BeatmapManager.pendingSongData = null; BeatmapManager.pendingDifficulty = -1; // Ensure time scale restored try { Time.timeScale = 1f; } catch {} // If GameManager and its blackMaskImage available, start coroutine on gm to fade to black then load if (gm != null && gm.blackMaskImage != null) { gm.StartCoroutine(FadeToBlackAndLoadOnGM("selectYourSongFirst", 0.25f)); } else { StartCoroutine(LoadSceneAsync("selectYourSongFirst")); } } // Coroutine that will be started on the GameManager instance so that the gm's MonoBehaviour runs it private IEnumerator FadeToBlackAndLoadOnGM(string sceneName, float duration) { if (gm == null || gm.blackMaskImage == null) { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); while (!asyncLoad.isDone) yield return null; yield break; } var blackMask = gm.blackMaskImage; // ensure image active if (!blackMask.gameObject.activeSelf) blackMask.gameObject.SetActive(true); float startA = blackMask.color.a; float elapsed = 0f; // enable raycast target while fading to block input try { blackMask.raycastTarget = true; } catch {} while (elapsed < duration) { elapsed += Time.unscaledDeltaTime; float frac = Mathf.Clamp01(elapsed / Mathf.Max(0.0001f, duration)); Color c = blackMask.color; c.a = Mathf.Lerp(startA, 1f, frac); blackMask.color = c; yield return null; } // ensure fully opaque Color fc = blackMask.color; fc.a = 1f; blackMask.color = fc; // load target scene AsyncOperation asyncOp = SceneManager.LoadSceneAsync(sceneName); while (!asyncOp.isDone) { yield return null; } } /// /// Documentation text normalized. /// Documentation text normalized. /// private void SetMvpHeroImageFromTopScorer() { if (mvp_hero_hd_image == null) { Debug.LogWarning("[SettlementController] mvp_hero_hd_image is not assigned"); return; } int topIndex = -1; int[] scores = new int[5]; if (sm != null) { scores[0] = sm.red_idolScore_sum; scores[1] = sm.green_idolScore_sum; scores[2] = sm.yellow_idolScore_sum; scores[3] = sm.purple_idolScore_sum; scores[4] = sm.blue_idolScore_sum; } else { // fallback: read AllyCombatant.currentScore from scene for (int i = 0; i < 5; i++) { var go = GameObject.Find($"ally_0{ i+1 }"); if (go != null) { var ac = go.GetComponent(); scores[i] = ac != null ? ac.currentScore : 0; } else scores[i] = 0; } } int max = -1; for (int i = 0; i < scores.Length; i++) { if (scores[i] > max) { max = scores[i]; topIndex = i; } } if (topIndex < 0 || max <= 0) { Debug.Log("[SettlementController] No top scorer found or scores are all zero"); // hide image and mvp object if (mvp_object != null) mvp_object.SetActive(false); if (mvp_hero_hd_image != null) { mvp_hero_hd_image.sprite = null; mvp_hero_hd_image.color = new Color(mvp_hero_hd_image.color.r, mvp_hero_hd_image.color.g, mvp_hero_hd_image.color.b, 0f); } return; } // Try to resolve AllyHero_SO for the winning slot AllyHero_SO heroSO = null; // try SkillBuilder cache if (SkillBuilder.Instance != null) { try { heroSO = SkillBuilder.Instance.GetAllyHeroSOBySlot(topIndex); } catch { heroSO = null; } } // fallback: use teamUIController slot ids to find hero id -> load SO if (heroSO == null && teamUIController.Instance != null && teamUIController.Instance.allySlotIds != null) { int allyId = -1; if (topIndex >= 0 && topIndex < teamUIController.Instance.allySlotIds.Count) allyId = teamUIController.Instance.allySlotIds[topIndex]; if (allyId > 0) { if (_cachedAllyHeroSOs == null || _cachedAllyHeroSOs.Length == 0) { _cachedAllyHeroSOs = Resources.LoadAll(""); } foreach (var a in _cachedAllyHeroSOs) { if (a != null && a.ally_heroID == allyId) { heroSO = a; break; } } } } // final fallback: try direct Resources lookup by scanning all and picking first non-null for slot // REMOVED: This fallback causes a default character to appear when no hero is selected. /* if (heroSO == null) { if (_cachedAllyHeroSOs == null || _cachedAllyHeroSOs.Length == 0) { _cachedAllyHeroSOs = Resources.LoadAll(""); } if (_cachedAllyHeroSOs != null && _cachedAllyHeroSOs.Length > 0) { // attempt to match by name or just pick first heroSO = _cachedAllyHeroSOs[0] as AllyHero_SO; } } */ if (heroSO != null) { if (mvp_object != null) mvp_object.SetActive(true); if (heroSO.ally_hero_HD_image != null && mvp_hero_hd_image != null) { mvp_hero_hd_image.sprite = heroSO.ally_hero_HD_image; mvp_hero_hd_image.color = new Color(mvp_hero_hd_image.color.r, mvp_hero_hd_image.color.g, mvp_hero_hd_image.color.b, 1f); } // 更新 MVP 详情界面(名字、头像、分数) if (mvp_heroName != null) mvp_heroName.text = heroSO.ally_heroName; if (mvp_heroIcon != null) mvp_heroIcon.sprite = heroSO.ally_hero_squareProfile; if (mvp_score != null) mvp_score.text = max.ToString(); Debug.Log($"[SettlementController] MVP assigned from slot {topIndex+1} with score {max}: {heroSO.ally_heroName}"); } else { if (mvp_object != null) mvp_object.SetActive(false); if (mvp_hero_hd_image != null) { mvp_hero_hd_image.sprite = null; mvp_hero_hd_image.color = new Color(mvp_hero_hd_image.color.r, mvp_hero_hd_image.color.g, mvp_hero_hd_image.color.b, 0f); } Debug.LogWarning("[SettlementController] MVP SO or HD image not found"); } } }