notebook实装功能,galgame继续,一些bug修复
This commit is contained in:
@@ -23,10 +23,26 @@ public class GameManager : MonoBehaviour
|
||||
[Header("canvas")]
|
||||
public GameObject startCanvas;
|
||||
public CanvasGroup cg_startCanvas;
|
||||
public GameObject extraStartObject;
|
||||
private CanvasGroup extraStartCG;
|
||||
public float canvasFadeTime;
|
||||
private Coroutine fade_canvasGroup;
|
||||
public CanvasGroup settleCG;
|
||||
|
||||
[Header("Start Canvas Anim")]
|
||||
[SerializeField] private bool enableStartCanvasDetailedAnim = true;
|
||||
[SerializeField] private float startCanvasBtmFadeIn = 0.28f;
|
||||
[SerializeField] private float startCanvasBtmFadeOut = 0.22f;
|
||||
[SerializeField] private int startCanvasFlashCount = 2;
|
||||
[SerializeField] private float startCanvasFlashUnit = 0.055f;
|
||||
[SerializeField] private float startCanvasImageFadeIn = 0.2f;
|
||||
[SerializeField] private float startCanvasMoveInDuration = 0.3f;
|
||||
[SerializeField] private float startCanvasMoveOutDuration = 0.22f;
|
||||
[SerializeField] private float startCanvasMoveStagger = 0.06f;
|
||||
[SerializeField] private float startCanvasMoveOffsetY = 80f;
|
||||
[SerializeField] private float startCanvasPanelExitOffsetY = 120f;
|
||||
[SerializeField] private float startCanvasEntryGap = 0.04f;
|
||||
|
||||
[Header("buttons")]
|
||||
public Button startGame;
|
||||
public Button backtoSelectingPage;
|
||||
@@ -67,6 +83,35 @@ public class GameManager : MonoBehaviour
|
||||
private SongData currentSong;
|
||||
private bool timeRecorded = false;
|
||||
|
||||
private Coroutine startCanvasIntroRoutine;
|
||||
private bool startCanvasIntroCompleted = false;
|
||||
private bool startCanvasExitStarted = false;
|
||||
|
||||
private RectTransform startupCanvasRoot;
|
||||
private RectTransform startup_btm;
|
||||
private RectTransform startup_boarder;
|
||||
private RectTransform startup_image;
|
||||
private RectTransform startup_text;
|
||||
private RectTransform startup_start;
|
||||
private RectTransform startup_escape;
|
||||
private RectTransform startup_tips;
|
||||
private RectTransform startup_uiBack;
|
||||
|
||||
private CanvasGroup startup_btmCg;
|
||||
private CanvasGroup startup_boarderCg;
|
||||
private CanvasGroup startup_imageCg;
|
||||
private CanvasGroup startup_textCg;
|
||||
private CanvasGroup startup_startCg;
|
||||
private CanvasGroup startup_escapeCg;
|
||||
private CanvasGroup startup_tipsCg;
|
||||
private CanvasGroup startup_uiBackCg;
|
||||
|
||||
private Vector2 startup_boarderBasePos;
|
||||
private Vector2 startup_imageBasePos;
|
||||
private Vector2 startup_textBasePos;
|
||||
private Vector2 startup_startBasePos;
|
||||
private Vector2 startup_escapeBasePos;
|
||||
|
||||
private void SubscribeToPauseManager()
|
||||
{
|
||||
if (pauseSubscribed) return;
|
||||
@@ -299,8 +344,15 @@ public class GameManager : MonoBehaviour
|
||||
Debug.LogWarning("[GameManager] No SongData found for statistics tracking");
|
||||
}
|
||||
|
||||
// Start a new per-level skill timeline log (overwrites previous level log).
|
||||
string skillLogSessionName = currentSong != null ? currentSong.songName : "UnknownSong";
|
||||
GameplaySkillLogger.BeginSession(skillLogSessionName);
|
||||
Debug.Log($"[GameManager] Skill timeline log reset: {GameplaySkillLogger.LogFilePath}");
|
||||
|
||||
// Ensure start canvas and its CanvasGroup are active and visible at scene start
|
||||
if (startCanvas != null && !startCanvas.activeSelf) startCanvas.SetActive(true);
|
||||
if (startCanvas != null && startCanvas.transform.localScale.sqrMagnitude < 0.0001f)
|
||||
startCanvas.transform.localScale = Vector3.one;
|
||||
if (cg_startCanvas != null)
|
||||
{
|
||||
cg_startCanvas.alpha = 1f;
|
||||
@@ -308,6 +360,18 @@ public class GameManager : MonoBehaviour
|
||||
cg_startCanvas.blocksRaycasts = true;
|
||||
}
|
||||
|
||||
// Initialize extra start object and its CanvasGroup
|
||||
if (extraStartObject != null)
|
||||
{
|
||||
extraStartCG = extraStartObject.GetComponent<CanvasGroup>();
|
||||
}
|
||||
|
||||
startCanvasExitStarted = false;
|
||||
startCanvasIntroCompleted = false;
|
||||
InitializeStartCanvasAnimRefs();
|
||||
PrepareStartCanvasEntryState();
|
||||
StartStartCanvasIntro();
|
||||
|
||||
// Try to apply assigned SongData (if any) to background and UI after scene load
|
||||
StartCoroutine(WaitAndApplyAssignedSong(2f));
|
||||
|
||||
@@ -477,12 +541,28 @@ public class GameManager : MonoBehaviour
|
||||
StopCoroutine(fade_canvasGroup);
|
||||
fade_canvasGroup = null;
|
||||
}
|
||||
|
||||
if (startCanvasIntroRoutine != null)
|
||||
{
|
||||
StopCoroutine(startCanvasIntroRoutine);
|
||||
startCanvasIntroRoutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Start the fade coroutine for the start canvas group (safe to call multiple times)
|
||||
private void StartFadeStartCanvas()
|
||||
{
|
||||
if (cg_startCanvas == null || startCanvas == null) return;
|
||||
if (startCanvasExitStarted) return;
|
||||
startCanvasExitStarted = true;
|
||||
|
||||
if (startCanvasIntroRoutine != null)
|
||||
{
|
||||
StopCoroutine(startCanvasIntroRoutine);
|
||||
startCanvasIntroRoutine = null;
|
||||
}
|
||||
SetStartButtonsInteractable(false);
|
||||
|
||||
if (fade_canvasGroup != null) StopCoroutine(fade_canvasGroup);
|
||||
fade_canvasGroup = StartCoroutine(FadeStartCanvasCoroutine(canvasFadeTime));
|
||||
}
|
||||
@@ -491,13 +571,70 @@ public class GameManager : MonoBehaviour
|
||||
{
|
||||
if (cg_startCanvas == null || startCanvas == null) yield break;
|
||||
|
||||
if (enableStartCanvasDetailedAnim)
|
||||
{
|
||||
ApplyStartCanvasEntryFinalState();
|
||||
|
||||
// text / START / escape: sequential move down + fade out
|
||||
yield return StartCoroutine(AnimateMoveAndFade(startup_text, startup_textCg, startup_textBasePos, startup_textBasePos + new Vector2(0f, -startCanvasMoveOffsetY), 1f, 0f, startCanvasMoveOutDuration, false));
|
||||
yield return WaitRealtime(startCanvasMoveStagger);
|
||||
yield return StartCoroutine(AnimateMoveAndFade(startup_start, startup_startCg, startup_startBasePos, startup_startBasePos + new Vector2(0f, -startCanvasMoveOffsetY), 1f, 0f, startCanvasMoveOutDuration, false));
|
||||
yield return WaitRealtime(startCanvasMoveStagger);
|
||||
yield return StartCoroutine(AnimateMoveAndFade(startup_escape, startup_escapeCg, startup_escapeBasePos, startup_escapeBasePos + new Vector2(0f, -startCanvasMoveOffsetY), 1f, 0f, startCanvasMoveOutDuration, false));
|
||||
|
||||
// TIPS / UIback: flicker out
|
||||
Coroutine tipsOut = null;
|
||||
Coroutine uiBackOut = null;
|
||||
if (startup_tipsCg != null) tipsOut = StartCoroutine(FlashOut(startup_tipsCg, startCanvasFlashCount, startCanvasFlashUnit));
|
||||
if (startup_uiBackCg != null) uiBackOut = StartCoroutine(FlashOut(startup_uiBackCg, startCanvasFlashCount, startCanvasFlashUnit));
|
||||
|
||||
// boarder + Image: wait for text/START/escape, then move down + fade
|
||||
Coroutine boarderOut = StartCoroutine(AnimateMoveAndFade(startup_boarder, startup_boarderCg, startup_boarderBasePos, startup_boarderBasePos + new Vector2(0f, -startCanvasPanelExitOffsetY), 1f, 0f, startCanvasMoveOutDuration, false));
|
||||
Coroutine imageOut = StartCoroutine(AnimateMoveAndFade(startup_image, startup_imageCg, startup_imageBasePos, startup_imageBasePos + new Vector2(0f, -startCanvasPanelExitOffsetY), 1f, 0f, startCanvasMoveOutDuration, false));
|
||||
if (boarderOut != null || imageOut != null)
|
||||
yield return WaitRealtime(startCanvasMoveOutDuration + 0.01f);
|
||||
|
||||
if (tipsOut != null || uiBackOut != null)
|
||||
yield return WaitRealtime(startCanvasFlashUnit * Mathf.Max(2, startCanvasFlashCount * 2) + 0.01f);
|
||||
|
||||
// btm fade out at last
|
||||
yield return StartCoroutine(FadeCanvasGroupAlpha(startup_btmCg, 1f, 0f, startCanvasBtmFadeOut, false));
|
||||
|
||||
cg_startCanvas.alpha = 0f;
|
||||
cg_startCanvas.interactable = false;
|
||||
cg_startCanvas.blocksRaycasts = false;
|
||||
startCanvas.SetActive(false);
|
||||
|
||||
if (extraStartCG != null)
|
||||
{
|
||||
extraStartCG.alpha = 0f;
|
||||
extraStartCG.interactable = false;
|
||||
extraStartCG.blocksRaycasts = false;
|
||||
if (extraStartObject != null) extraStartObject.SetActive(false);
|
||||
}
|
||||
|
||||
fade_canvasGroup = null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
float elapsed = 0f;
|
||||
float startA = cg_startCanvas.alpha;
|
||||
while (elapsed < duration)
|
||||
{
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
float frac = Mathf.Clamp01(elapsed / Mathf.Max(0.0001f, duration));
|
||||
cg_startCanvas.alpha = Mathf.Lerp(startA, 0f, frac);
|
||||
float currentAlpha = Mathf.Lerp(startA, 0f, frac);
|
||||
|
||||
cg_startCanvas.alpha = currentAlpha;
|
||||
if (extraStartCG != null)
|
||||
{
|
||||
extraStartCG.alpha = currentAlpha;
|
||||
// Sync raycast state with alpha
|
||||
bool visible = currentAlpha > 0.001f;
|
||||
extraStartCG.interactable = visible;
|
||||
extraStartCG.blocksRaycasts = visible;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
@@ -505,9 +642,339 @@ public class GameManager : MonoBehaviour
|
||||
cg_startCanvas.interactable = false;
|
||||
cg_startCanvas.blocksRaycasts = false;
|
||||
startCanvas.SetActive(false);
|
||||
|
||||
if (extraStartCG != null)
|
||||
{
|
||||
extraStartCG.alpha = 0f;
|
||||
extraStartCG.interactable = false;
|
||||
extraStartCG.blocksRaycasts = false;
|
||||
if (extraStartObject != null) extraStartObject.SetActive(false);
|
||||
}
|
||||
|
||||
fade_canvasGroup = null;
|
||||
}
|
||||
|
||||
private void StartStartCanvasIntro()
|
||||
{
|
||||
if (!enableStartCanvasDetailedAnim)
|
||||
{
|
||||
ApplyStartCanvasEntryFinalState();
|
||||
startCanvasIntroCompleted = true;
|
||||
SetStartButtonsInteractable(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (startCanvasIntroRoutine != null)
|
||||
{
|
||||
StopCoroutine(startCanvasIntroRoutine);
|
||||
startCanvasIntroRoutine = null;
|
||||
}
|
||||
startCanvasIntroRoutine = StartCoroutine(StartCanvasIntroCoroutine());
|
||||
}
|
||||
|
||||
private IEnumerator StartCanvasIntroCoroutine()
|
||||
{
|
||||
startCanvasIntroCompleted = false;
|
||||
SetStartButtonsInteractable(false);
|
||||
|
||||
yield return StartCoroutine(FadeCanvasGroupAlpha(startup_btmCg, 0f, 1f, startCanvasBtmFadeIn, false));
|
||||
|
||||
float flashDuration = startCanvasFlashUnit * Mathf.Max(2, startCanvasFlashCount * 2);
|
||||
Coroutine boarderIn = null;
|
||||
Coroutine tipsIn = null;
|
||||
Coroutine uiBackIn = null;
|
||||
|
||||
if (startup_boarderCg != null) boarderIn = StartCoroutine(FlashIn(startup_boarderCg, startCanvasFlashCount, startCanvasFlashUnit, false));
|
||||
if (startup_tipsCg != null) tipsIn = StartCoroutine(FlashIn(startup_tipsCg, startCanvasFlashCount, startCanvasFlashUnit, false));
|
||||
if (startup_uiBackCg != null) uiBackIn = StartCoroutine(FlashIn(startup_uiBackCg, startCanvasFlashCount, startCanvasFlashUnit, false));
|
||||
if (boarderIn != null || tipsIn != null || uiBackIn != null)
|
||||
yield return WaitRealtime(flashDuration + 0.01f);
|
||||
|
||||
yield return WaitRealtime(startCanvasEntryGap);
|
||||
yield return StartCoroutine(FadeCanvasGroupAlpha(startup_imageCg, 0f, 1f, startCanvasImageFadeIn, false));
|
||||
|
||||
Vector2 textFrom = startup_textBasePos + new Vector2(0f, -startCanvasMoveOffsetY);
|
||||
Vector2 startFrom = startup_startBasePos + new Vector2(0f, -startCanvasMoveOffsetY);
|
||||
Vector2 escapeFrom = startup_escapeBasePos + new Vector2(0f, -startCanvasMoveOffsetY);
|
||||
|
||||
yield return StartCoroutine(AnimateMoveAndFade(startup_text, startup_textCg, textFrom, startup_textBasePos, 0f, 1f, startCanvasMoveInDuration, false));
|
||||
yield return WaitRealtime(startCanvasMoveStagger);
|
||||
yield return StartCoroutine(AnimateMoveAndFade(startup_start, startup_startCg, startFrom, startup_startBasePos, 0f, 1f, startCanvasMoveInDuration, true));
|
||||
yield return WaitRealtime(startCanvasMoveStagger);
|
||||
yield return StartCoroutine(AnimateMoveAndFade(startup_escape, startup_escapeCg, escapeFrom, startup_escapeBasePos, 0f, 1f, startCanvasMoveInDuration, true));
|
||||
|
||||
startCanvasIntroCompleted = true;
|
||||
SetStartButtonsInteractable(true);
|
||||
startCanvasIntroRoutine = null;
|
||||
}
|
||||
|
||||
private void InitializeStartCanvasAnimRefs()
|
||||
{
|
||||
startupCanvasRoot = null;
|
||||
startup_btm = null;
|
||||
startup_boarder = null;
|
||||
startup_image = null;
|
||||
startup_text = null;
|
||||
startup_start = null;
|
||||
startup_escape = null;
|
||||
startup_tips = null;
|
||||
startup_uiBack = null;
|
||||
|
||||
startup_btmCg = null;
|
||||
startup_boarderCg = null;
|
||||
startup_imageCg = null;
|
||||
startup_textCg = null;
|
||||
startup_startCg = null;
|
||||
startup_escapeCg = null;
|
||||
startup_tipsCg = null;
|
||||
startup_uiBackCg = null;
|
||||
|
||||
if (startCanvas == null) return;
|
||||
|
||||
Transform root = startCanvas.transform;
|
||||
startupCanvasRoot = FindRectByName(root, "startupCanvas");
|
||||
if (startupCanvasRoot == null) startupCanvasRoot = root as RectTransform;
|
||||
if (startupCanvasRoot == null) return;
|
||||
|
||||
startup_btm = FindDirectChildRectByName(startupCanvasRoot, "btm");
|
||||
startup_boarder = FindDirectChildRectByName(startupCanvasRoot, "boarder");
|
||||
startup_image = FindDirectChildRectByName(startupCanvasRoot, "Image");
|
||||
startup_text = FindDirectChildRectByName(startupCanvasRoot, "text");
|
||||
startup_start = FindDirectChildRectByName(startupCanvasRoot, "START");
|
||||
startup_escape = FindDirectChildRectByName(startupCanvasRoot, "escape");
|
||||
startup_tips = FindDirectChildRectByName(startupCanvasRoot, "TIPS");
|
||||
startup_uiBack = FindDirectChildRectByName(startupCanvasRoot, "UIback");
|
||||
|
||||
if (startup_tips == null) startup_tips = FindRectByName(startupCanvasRoot, "TIPS");
|
||||
if (startup_uiBack == null) startup_uiBack = FindRectByName(startupCanvasRoot, "UIback");
|
||||
|
||||
startup_btmCg = GetOrAddCanvasGroup(startup_btm);
|
||||
startup_boarderCg = GetOrAddCanvasGroup(startup_boarder);
|
||||
startup_imageCg = GetOrAddCanvasGroup(startup_image);
|
||||
startup_textCg = GetOrAddCanvasGroup(startup_text);
|
||||
startup_startCg = GetOrAddCanvasGroup(startup_start);
|
||||
startup_escapeCg = GetOrAddCanvasGroup(startup_escape);
|
||||
startup_tipsCg = GetOrAddCanvasGroup(startup_tips);
|
||||
startup_uiBackCg = GetOrAddCanvasGroup(startup_uiBack);
|
||||
|
||||
if (startup_boarder != null) startup_boarderBasePos = startup_boarder.anchoredPosition;
|
||||
if (startup_image != null) startup_imageBasePos = startup_image.anchoredPosition;
|
||||
if (startup_text != null) startup_textBasePos = startup_text.anchoredPosition;
|
||||
if (startup_start != null) startup_startBasePos = startup_start.anchoredPosition;
|
||||
if (startup_escape != null) startup_escapeBasePos = startup_escape.anchoredPosition;
|
||||
}
|
||||
|
||||
private void PrepareStartCanvasEntryState()
|
||||
{
|
||||
if (cg_startCanvas == null || startCanvas == null) return;
|
||||
|
||||
if (!startCanvas.activeSelf) startCanvas.SetActive(true);
|
||||
cg_startCanvas.alpha = 1f;
|
||||
cg_startCanvas.interactable = true;
|
||||
cg_startCanvas.blocksRaycasts = true;
|
||||
|
||||
if (extraStartCG != null)
|
||||
{
|
||||
if (extraStartObject != null && !extraStartObject.activeSelf) extraStartObject.SetActive(true);
|
||||
extraStartCG.alpha = 1f;
|
||||
extraStartCG.interactable = true;
|
||||
extraStartCG.blocksRaycasts = true;
|
||||
}
|
||||
|
||||
if (!enableStartCanvasDetailedAnim)
|
||||
{
|
||||
ApplyStartCanvasEntryFinalState();
|
||||
return;
|
||||
}
|
||||
|
||||
SetCanvasGroupAlpha(startup_btmCg, 0f, false);
|
||||
SetCanvasGroupAlpha(startup_boarderCg, 0f, false);
|
||||
SetCanvasGroupAlpha(startup_imageCg, 0f, false);
|
||||
SetCanvasGroupAlpha(startup_textCg, 0f, false);
|
||||
SetCanvasGroupAlpha(startup_startCg, 0f, true);
|
||||
SetCanvasGroupAlpha(startup_escapeCg, 0f, true);
|
||||
SetCanvasGroupAlpha(startup_tipsCg, 0f, false);
|
||||
SetCanvasGroupAlpha(startup_uiBackCg, 0f, false);
|
||||
|
||||
if (startup_boarder != null) startup_boarder.anchoredPosition = startup_boarderBasePos;
|
||||
if (startup_image != null) startup_image.anchoredPosition = startup_imageBasePos;
|
||||
if (startup_text != null) startup_text.anchoredPosition = startup_textBasePos + new Vector2(0f, -startCanvasMoveOffsetY);
|
||||
if (startup_start != null) startup_start.anchoredPosition = startup_startBasePos + new Vector2(0f, -startCanvasMoveOffsetY);
|
||||
if (startup_escape != null) startup_escape.anchoredPosition = startup_escapeBasePos + new Vector2(0f, -startCanvasMoveOffsetY);
|
||||
}
|
||||
|
||||
private void ApplyStartCanvasEntryFinalState()
|
||||
{
|
||||
if (startup_boarder != null) startup_boarder.anchoredPosition = startup_boarderBasePos;
|
||||
if (startup_image != null) startup_image.anchoredPosition = startup_imageBasePos;
|
||||
if (startup_text != null) startup_text.anchoredPosition = startup_textBasePos;
|
||||
if (startup_start != null) startup_start.anchoredPosition = startup_startBasePos;
|
||||
if (startup_escape != null) startup_escape.anchoredPosition = startup_escapeBasePos;
|
||||
|
||||
SetCanvasGroupAlpha(startup_btmCg, 1f, false);
|
||||
SetCanvasGroupAlpha(startup_boarderCg, 1f, false);
|
||||
SetCanvasGroupAlpha(startup_imageCg, 1f, false);
|
||||
SetCanvasGroupAlpha(startup_textCg, 1f, false);
|
||||
SetCanvasGroupAlpha(startup_startCg, 1f, true);
|
||||
SetCanvasGroupAlpha(startup_escapeCg, 1f, true);
|
||||
SetCanvasGroupAlpha(startup_tipsCg, 1f, false);
|
||||
SetCanvasGroupAlpha(startup_uiBackCg, 1f, false);
|
||||
}
|
||||
|
||||
private void SetStartButtonsInteractable(bool interactable)
|
||||
{
|
||||
if (startGame != null) startGame.interactable = interactable;
|
||||
if (backtoSelectingPage != null) backtoSelectingPage.interactable = interactable;
|
||||
}
|
||||
|
||||
private IEnumerator AnimateMoveAndFade(RectTransform rt, CanvasGroup cg, Vector2 from, Vector2 to, float fromAlpha, float toAlpha, float duration, bool allowRaycastWhenVisible)
|
||||
{
|
||||
if (rt == null || cg == null) yield break;
|
||||
|
||||
float dur = Mathf.Max(0.01f, duration);
|
||||
float elapsed = 0f;
|
||||
rt.anchoredPosition = from;
|
||||
SetCanvasGroupAlpha(cg, fromAlpha, allowRaycastWhenVisible);
|
||||
|
||||
while (elapsed < dur)
|
||||
{
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
|
||||
rt.anchoredPosition = Vector2.LerpUnclamped(from, to, t);
|
||||
float a = Mathf.LerpUnclamped(fromAlpha, toAlpha, t);
|
||||
SetCanvasGroupAlpha(cg, a, allowRaycastWhenVisible);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
rt.anchoredPosition = to;
|
||||
SetCanvasGroupAlpha(cg, toAlpha, allowRaycastWhenVisible);
|
||||
}
|
||||
|
||||
private IEnumerator FadeCanvasGroupAlpha(CanvasGroup cg, float fromAlpha, float toAlpha, float duration, bool allowRaycastWhenVisible)
|
||||
{
|
||||
if (cg == null) yield break;
|
||||
float dur = Mathf.Max(0.01f, duration);
|
||||
float elapsed = 0f;
|
||||
SetCanvasGroupAlpha(cg, fromAlpha, allowRaycastWhenVisible);
|
||||
|
||||
while (elapsed < dur)
|
||||
{
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
|
||||
float a = Mathf.LerpUnclamped(fromAlpha, toAlpha, t);
|
||||
SetCanvasGroupAlpha(cg, a, allowRaycastWhenVisible);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
SetCanvasGroupAlpha(cg, toAlpha, allowRaycastWhenVisible);
|
||||
}
|
||||
|
||||
private IEnumerator FlashIn(CanvasGroup cg, int flashes, float unitDuration, bool allowRaycastWhenVisible)
|
||||
{
|
||||
if (cg == null) yield break;
|
||||
int count = Mathf.Max(1, flashes);
|
||||
float step = Mathf.Max(0.01f, unitDuration);
|
||||
SetCanvasGroupAlpha(cg, 0f, allowRaycastWhenVisible);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
SetCanvasGroupAlpha(cg, 1f, allowRaycastWhenVisible);
|
||||
yield return WaitRealtime(step);
|
||||
if (i < count - 1)
|
||||
{
|
||||
SetCanvasGroupAlpha(cg, 0f, allowRaycastWhenVisible);
|
||||
yield return WaitRealtime(step);
|
||||
}
|
||||
}
|
||||
SetCanvasGroupAlpha(cg, 1f, allowRaycastWhenVisible);
|
||||
}
|
||||
|
||||
private IEnumerator FlashOut(CanvasGroup cg, int flashes, float unitDuration)
|
||||
{
|
||||
if (cg == null) yield break;
|
||||
int count = Mathf.Max(1, flashes);
|
||||
float step = Mathf.Max(0.01f, unitDuration);
|
||||
SetCanvasGroupAlpha(cg, 1f, false);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
SetCanvasGroupAlpha(cg, 0f, false);
|
||||
yield return WaitRealtime(step);
|
||||
if (i < count - 1)
|
||||
{
|
||||
SetCanvasGroupAlpha(cg, 1f, false);
|
||||
yield return WaitRealtime(step);
|
||||
}
|
||||
}
|
||||
SetCanvasGroupAlpha(cg, 0f, false);
|
||||
}
|
||||
|
||||
private IEnumerator WaitRealtime(float duration)
|
||||
{
|
||||
float elapsed = 0f;
|
||||
float dur = Mathf.Max(0f, duration);
|
||||
while (elapsed < dur)
|
||||
{
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static float EaseOutCubic(float t)
|
||||
{
|
||||
float inv = 1f - t;
|
||||
return 1f - inv * inv * inv;
|
||||
}
|
||||
|
||||
private static CanvasGroup GetOrAddCanvasGroup(RectTransform rt)
|
||||
{
|
||||
if (rt == null) return null;
|
||||
CanvasGroup cg = rt.GetComponent<CanvasGroup>();
|
||||
if (cg == null) cg = rt.gameObject.AddComponent<CanvasGroup>();
|
||||
return cg;
|
||||
}
|
||||
|
||||
private static void SetCanvasGroupAlpha(CanvasGroup cg, float alpha, bool allowRaycastWhenVisible)
|
||||
{
|
||||
if (cg == null) return;
|
||||
float a = Mathf.Clamp01(alpha);
|
||||
cg.alpha = a;
|
||||
bool visible = a > 0.001f;
|
||||
cg.interactable = visible && allowRaycastWhenVisible;
|
||||
cg.blocksRaycasts = visible && allowRaycastWhenVisible;
|
||||
}
|
||||
|
||||
private static RectTransform FindDirectChildRectByName(Transform root, string exactName)
|
||||
{
|
||||
if (root == null || string.IsNullOrEmpty(exactName)) return null;
|
||||
for (int i = 0; i < root.childCount; i++)
|
||||
{
|
||||
Transform c = root.GetChild(i);
|
||||
if (c == null) continue;
|
||||
if (string.Equals(c.name, exactName, System.StringComparison.OrdinalIgnoreCase))
|
||||
return c as RectTransform;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static RectTransform FindRectByName(Transform root, string exactName)
|
||||
{
|
||||
Transform t = FindDescendantByName(root, exactName);
|
||||
return t as RectTransform;
|
||||
}
|
||||
|
||||
private static Transform FindDescendantByName(Transform root, string exactName)
|
||||
{
|
||||
if (root == null || string.IsNullOrEmpty(exactName)) return null;
|
||||
Transform[] all = root.GetComponentsInChildren<Transform>(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 void OnAllNotesSpawned()
|
||||
{
|
||||
// All notes have been spawned - trigger settlement decision logic
|
||||
|
||||
@@ -60,11 +60,6 @@ public class HoldNote : BaseNote
|
||||
[Range(1f, 2f)]
|
||||
public float holdWindowMultiplier = 1.3f;
|
||||
|
||||
[Header("Custom Animation")]
|
||||
public GameObject holdAnimationPrefab;
|
||||
private GameObject activeHoldAnimationInstance;
|
||||
private Coroutine delayedHoldAnimationCoroutine;
|
||||
|
||||
public float visualSpeedMultiplier = 1f; // injected from NoteSpawner to adapt windows when visual speed changes
|
||||
|
||||
// track when the hold actually started (real-time) so we can compute held fraction
|
||||
@@ -121,6 +116,7 @@ public class HoldNote : BaseNote
|
||||
int baseScore = 0;
|
||||
|
||||
var bmm = ally != null ? ally.bmm : null;
|
||||
if (bmm == null) bmm = BeatmapManager.Instance;
|
||||
if (bmm == null) bmm = GetBeatmapManagerCached();
|
||||
if (bmm != null) baseScore = Mathf.Max(0, bmm.perNoteScore);
|
||||
|
||||
@@ -322,58 +318,6 @@ public class HoldNote : BaseNote
|
||||
}
|
||||
}
|
||||
|
||||
private void StartHoldEffects()
|
||||
{
|
||||
StopHoldEffects(); // cleanup any existing
|
||||
if (gameObject.activeInHierarchy)
|
||||
{
|
||||
delayedHoldAnimationCoroutine = StartCoroutine(DelayedStartHoldEffects());
|
||||
}
|
||||
}
|
||||
|
||||
private void StopHoldEffects()
|
||||
{
|
||||
if (delayedHoldAnimationCoroutine != null)
|
||||
{
|
||||
StopCoroutine(delayedHoldAnimationCoroutine);
|
||||
delayedHoldAnimationCoroutine = null;
|
||||
}
|
||||
if (activeHoldAnimationInstance != null)
|
||||
{
|
||||
Destroy(activeHoldAnimationInstance);
|
||||
activeHoldAnimationInstance = null;
|
||||
}
|
||||
AnimationController.Global?.StopHoldParticles();
|
||||
}
|
||||
|
||||
private IEnumerator DelayedStartHoldEffects()
|
||||
{
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
|
||||
// Start standard particles
|
||||
AnimationController.Global?.StartHoldParticles(noteColor);
|
||||
|
||||
// Instantiate custom prefab if assigned
|
||||
if (holdAnimationPrefab != null)
|
||||
{
|
||||
// find spawn position: prefer the track effect position if available from AnimationController
|
||||
Vector3 spawnPos = transform.position;
|
||||
var ac = AnimationController.Global;
|
||||
if (ac != null)
|
||||
{
|
||||
// Logic to find slot position (simplified: use ac's position or the note's)
|
||||
// In AnimationController.PlayDestroyAnimation, it uses redEffect.transform.position etc.
|
||||
// We don't have direct access to those private fields, but we can assume
|
||||
// the note is at the judge line when this starts.
|
||||
}
|
||||
|
||||
activeHoldAnimationInstance = Instantiate(holdAnimationPrefab, spawnPos, Quaternion.identity);
|
||||
|
||||
// Optionally parent to the note or track
|
||||
// activeHoldAnimationInstance.transform.SetParent(this.transform, false);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator DelayedDisableAnimation(GameObject animationObject, float delay)
|
||||
{
|
||||
// No-op: we used to disable the shared AnimationController here which stopped coroutines.
|
||||
@@ -393,6 +337,7 @@ public class HoldNote : BaseNote
|
||||
|
||||
this.trackIndex = trackIndex;
|
||||
// also set BaseNote.TrackIndex so other systems using TrackIndex property work consistently
|
||||
this.TrackIndex = trackIndex;
|
||||
this.speed = speed;
|
||||
this.startTime = time;
|
||||
this.delay = delay;
|
||||
@@ -497,7 +442,7 @@ public class HoldNote : BaseNote
|
||||
if (!isHoldActive && jm != null && jm.IsStartJudged(noteID) && !jm.HasNoteReleased(noteID) && keyHeld)
|
||||
{
|
||||
isHoldActive = true;
|
||||
StartHoldEffects();
|
||||
AnimationController.Global?.StartHoldParticles(noteColor);
|
||||
if (debugEnabled) Debug.Log($"[HoldNote] Start particles started for {noteID} color={noteColor} at time={now:F3}");
|
||||
}
|
||||
|
||||
@@ -531,7 +476,7 @@ public class HoldNote : BaseNote
|
||||
if (isHoldActive && keyUp)
|
||||
{
|
||||
isHoldActive = false;
|
||||
StopHoldEffects();
|
||||
AnimationController.Global?.StopHoldParticles();
|
||||
if (debugEnabled) Debug.Log($"[HoldNote] KeyUp {keyToPress} detected. NoteID: {noteID}, Segment: {segment}, Type: {type}");
|
||||
|
||||
if (!hasReleased)
|
||||
@@ -706,7 +651,7 @@ public class HoldNote : BaseNote
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
isHoldActive = true;
|
||||
PlayHitAnimation();
|
||||
StartHoldEffects();
|
||||
AnimationController.Global?.StartHoldParticles(noteColor);
|
||||
|
||||
holdStartTime = pressTime;
|
||||
HoldNoteJudgePool.RegisterStart(noteID, pressTime, hitTime, scheduledEndTime, noteColor, trackIndex, (object)noteData);
|
||||
@@ -762,7 +707,7 @@ public class HoldNote : BaseNote
|
||||
isJudged = true;
|
||||
// Terminate input - mark key as released
|
||||
isHoldActive = false;
|
||||
StopHoldEffects();
|
||||
AnimationController.Global?.StopHoldParticles();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -932,7 +877,7 @@ public class HoldNote : BaseNote
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
isHoldActive = true;
|
||||
PlayHitAnimation();
|
||||
StartHoldEffects();
|
||||
AnimationController.Global?.StartHoldParticles(noteColor);
|
||||
|
||||
// record when the hold started so we can compute held fraction later
|
||||
holdStartTime = pressTime;
|
||||
@@ -948,7 +893,7 @@ public class HoldNote : BaseNote
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
isHoldActive = true;
|
||||
PlayHitAnimation();
|
||||
StartHoldEffects();
|
||||
AnimationController.Global?.StartHoldParticles(noteColor);
|
||||
|
||||
holdStartTime = pressTime;
|
||||
|
||||
@@ -962,7 +907,7 @@ public class HoldNote : BaseNote
|
||||
JudgeManager.Instance.RegisterStartJudged(noteID, true);
|
||||
isHoldActive = true;
|
||||
PlayHitAnimation();
|
||||
StartHoldEffects();
|
||||
AnimationController.Global?.StartHoldParticles(noteColor);
|
||||
|
||||
holdStartTime = pressTime;
|
||||
|
||||
@@ -1250,7 +1195,7 @@ public class HoldNote : BaseNote
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"[HoldNote] Hold end NotifyNoteHit called: TrackIndex={TrackIndex} trackIndexField={trackIndex} result={result} triggeredHold={triggeredHold} triggeredTap={triggeredTap}");
|
||||
}
|
||||
|
||||
StopHoldEffects();
|
||||
AnimationController.Global?.StopHoldParticles();
|
||||
|
||||
// Release the track lock immediately after end evaluation so the next hold can start on time
|
||||
ReleaseTrackJudgeLockSafe();
|
||||
@@ -1372,9 +1317,6 @@ public class HoldNote : BaseNote
|
||||
// Restore materials before returning to pool/reusing
|
||||
RestoreOriginalMaterials();
|
||||
|
||||
// Stop any active hold animations or delayed starts
|
||||
StopHoldEffects();
|
||||
|
||||
hasReleased = false;
|
||||
segment = NoteSegment.None;
|
||||
keyToPress = KeyCode.None;
|
||||
|
||||
@@ -13,6 +13,7 @@ public class HoldNoteController : MonoBehaviour
|
||||
private bool useAbsolutePositioning = false;
|
||||
private Vector3 spawnPosition;
|
||||
private float baseSpawnYOffset = 0f; // can be negative to offset upward for delayed segments
|
||||
private float visualOffset = 0f; // additional Y offset for visual compensation
|
||||
|
||||
private void Update()
|
||||
{
|
||||
@@ -44,6 +45,7 @@ public class HoldNoteController : MonoBehaviour
|
||||
activationTime = hitTime - travelTime;
|
||||
isMoving = false;
|
||||
useAbsolutePositioning = false;
|
||||
visualOffset = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -54,6 +56,7 @@ public class HoldNoteController : MonoBehaviour
|
||||
activationTime = Time.time + segmentDelay;
|
||||
isMoving = false;
|
||||
useAbsolutePositioning = false;
|
||||
visualOffset = 0f;
|
||||
}
|
||||
|
||||
public void SetSpeed(float newSpeed)
|
||||
@@ -66,11 +69,12 @@ public class HoldNoteController : MonoBehaviour
|
||||
/// spawnPoint should be the original spawn point for the lane.
|
||||
/// baseSpawnYOffset is applied to keep delayed segments offset upward (can be negative).
|
||||
/// </summary>
|
||||
public void ConfigureAbsolutePositioning(Vector3 spawnPoint, float newActivationTime, float spawnYOffset)
|
||||
public void ConfigureAbsolutePositioning(Vector3 spawnPoint, float newActivationTime, float spawnYOffset, float newVisualOffset = 0f)
|
||||
{
|
||||
spawnPosition = spawnPoint;
|
||||
activationTime = newActivationTime;
|
||||
baseSpawnYOffset = spawnYOffset;
|
||||
visualOffset = newVisualOffset;
|
||||
useAbsolutePositioning = true;
|
||||
isMoving = true;
|
||||
|
||||
@@ -81,6 +85,7 @@ public class HoldNoteController : MonoBehaviour
|
||||
public void StopMovement()
|
||||
{
|
||||
isMoving = false;
|
||||
visualOffset = 0f;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D collision)
|
||||
@@ -109,6 +114,7 @@ public class HoldNoteController : MonoBehaviour
|
||||
public bool UsesAbsolutePositioning => useAbsolutePositioning;
|
||||
public float BaseSpawnYOffset => baseSpawnYOffset;
|
||||
public Vector3 SpawnPosition => spawnPosition;
|
||||
public float VisualOffset => visualOffset;
|
||||
|
||||
private void ApplyAbsolutePosition(float time)
|
||||
{
|
||||
@@ -116,7 +122,8 @@ public class HoldNoteController : MonoBehaviour
|
||||
float travelDistance = speed * elapsed;
|
||||
|
||||
Vector3 newPos = spawnPosition;
|
||||
newPos.y -= (baseSpawnYOffset + travelDistance);
|
||||
// Apply visual offset (positive moves the note down its path earlier)
|
||||
newPos.y -= (baseSpawnYOffset + travelDistance + visualOffset);
|
||||
transform.position = newPos;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: 088ed1ed6b6731f43ad3166781e3e931, type: 3}
|
||||
m_Name: NoteJudgeConfig
|
||||
m_EditorClassIdentifier:
|
||||
perfectRange: 0.05
|
||||
greatRange: 0.1
|
||||
goodRange: 0.15
|
||||
missRange: 0.2
|
||||
perfectRange: 0.1
|
||||
greatRange: 0.15
|
||||
goodRange: 0.2
|
||||
missRange: 0.25
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Globalization;
|
||||
using UnityEngine;
|
||||
|
||||
public class Note : BaseNote
|
||||
@@ -55,12 +56,10 @@ public class Note : BaseNote
|
||||
|
||||
private static int ComputePmDeltaFromJudge(string judgeResult, AllyCombatant ally)
|
||||
{
|
||||
// If current character slot has no ally or is dead, then this corresponding track will also not get idol score
|
||||
if (ally == null || ally.IsDead) return 0;
|
||||
|
||||
int baseScore = 0;
|
||||
|
||||
var bmm = ally != null ? ally.bmm : null;
|
||||
if (bmm == null) bmm = BeatmapManager.Instance;
|
||||
if (bmm == null) bmm = GetBeatmapManagerCached();
|
||||
if (bmm != null) baseScore = Mathf.Max(0, bmm.perNoteScore);
|
||||
|
||||
@@ -109,6 +108,42 @@ public class Note : BaseNote
|
||||
sm.trackPerfectCounts[trackIndex] += 1;
|
||||
}
|
||||
|
||||
private string BuildNoteLogId()
|
||||
{
|
||||
if (noteData != null)
|
||||
{
|
||||
return "tap:"
|
||||
+ TrackIndex
|
||||
+ ":"
|
||||
+ noteData.time.ToString("0.###", CultureInfo.InvariantCulture)
|
||||
+ ":"
|
||||
+ gameObject.GetInstanceID();
|
||||
}
|
||||
|
||||
return "tap:"
|
||||
+ TrackIndex
|
||||
+ ":"
|
||||
+ hitTime.ToString("0.###", CultureInfo.InvariantCulture)
|
||||
+ ":"
|
||||
+ gameObject.GetInstanceID();
|
||||
}
|
||||
|
||||
private void LogTapJudge(string judgeResult, float rawOffsetMs, bool rewrittenToPerfect, bool autoplay, float actionTime)
|
||||
{
|
||||
GameplaySkillLogger.RecordJudgeResult(
|
||||
"Tap",
|
||||
"Single",
|
||||
TrackIndex,
|
||||
BuildNoteLogId(),
|
||||
judgeResult,
|
||||
rawOffsetMs,
|
||||
rewrittenToPerfect,
|
||||
autoplay,
|
||||
actionTime,
|
||||
hitTime,
|
||||
float.NaN);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
anim = GetComponent<AnimationController>();
|
||||
@@ -218,6 +253,7 @@ public class Note : BaseNote
|
||||
ScoreManager.Instance.RecordOffset(0f);
|
||||
|
||||
const string judgeResult = "Perfect";
|
||||
if (noteData != null) noteData.judgeResult = judgeResult;
|
||||
|
||||
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound(judgeResult);
|
||||
@@ -241,6 +277,7 @@ public class Note : BaseNote
|
||||
}
|
||||
|
||||
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
|
||||
LogTapJudge(judgeResult, 0f, false, true, pressTime);
|
||||
|
||||
Judge();
|
||||
}
|
||||
@@ -368,10 +405,13 @@ public class Note : BaseNote
|
||||
if (!string.IsNullOrEmpty(judgeResult))
|
||||
{
|
||||
string originalJudge = judgeResult;
|
||||
bool rewrittenToPerfect = false;
|
||||
if (TryRewriteNonMissJudgeToPerfect(TrackIndex, ref judgeResult))
|
||||
{
|
||||
rewrittenToPerfect = true;
|
||||
AdjustCountsAfterRewriteToPerfect(TrackIndex, originalJudge);
|
||||
}
|
||||
if (noteData != null) noteData.judgeResult = judgeResult;
|
||||
|
||||
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound(judgeResult);
|
||||
@@ -396,6 +436,7 @@ public class Note : BaseNote
|
||||
}
|
||||
|
||||
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
|
||||
LogTapJudge(judgeResult, rawOffsetMs, rewrittenToPerfect, false, pressTime);
|
||||
}
|
||||
|
||||
Judge();
|
||||
@@ -452,6 +493,7 @@ public class Note : BaseNote
|
||||
ScoreManager.Instance?.RecordOffset(0);
|
||||
|
||||
if (JudgeManager.IsDebugEnabled) Debug.Log($"{keyToPress} Miss");
|
||||
if (noteData != null) noteData.judgeResult = "Miss";
|
||||
|
||||
InputManager.Instance?.ShowJudgeResult(TrackIndex, "Miss");
|
||||
JudgeSoundManager.Instance?.PlayJudgeSound("Miss");
|
||||
@@ -476,6 +518,7 @@ public class Note : BaseNote
|
||||
}
|
||||
|
||||
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, "Miss", SkillDefinition.NoteTypeTrigger.Tap); } catch { }
|
||||
LogTapJudge("Miss", 0f, false, false, Time.time);
|
||||
|
||||
// notify global judge manager that this note has been finally judged (miss)
|
||||
JudgeManager.Instance?.NotifyNoteJudged();
|
||||
|
||||
@@ -5,6 +5,7 @@ using UnityEngine.Serialization;
|
||||
using UnityEngine.SceneManagement;
|
||||
using System.Collections;
|
||||
using UnityEngine.Audio;
|
||||
using DG.Tweening;
|
||||
|
||||
public class settlementController : MonoBehaviour
|
||||
{
|
||||
@@ -124,6 +125,10 @@ public class settlementController : MonoBehaviour
|
||||
[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;
|
||||
@@ -139,6 +144,11 @@ public class settlementController : MonoBehaviour
|
||||
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()
|
||||
{
|
||||
@@ -175,6 +185,16 @@ public class settlementController : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (settlementIntroCoroutine == null) return;
|
||||
if (!enableDetailedSettlementIntro) return;
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
SkipSettlementIntroAnimations();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (replay_thisGame != null)
|
||||
@@ -206,6 +226,12 @@ public class settlementController : MonoBehaviour
|
||||
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
|
||||
@@ -420,8 +446,12 @@ public class settlementController : MonoBehaviour
|
||||
|
||||
private void StartSettlementIntro()
|
||||
{
|
||||
skipIntroRequested = false;
|
||||
|
||||
if (!enableDetailedSettlementIntro)
|
||||
{
|
||||
if (settlementTeamLoader != null)
|
||||
settlementTeamLoader.ShowSpawnedCardsInstantly();
|
||||
StartCanvasFadeIn();
|
||||
return;
|
||||
}
|
||||
@@ -463,7 +493,16 @@ public class settlementController : MonoBehaviour
|
||||
Transform heroDApicTr = FindDescendantByName(rightRoot != null ? rightRoot : settleRoot, "heroDApic");
|
||||
Transform heroMaskTr = FindDescendantByName(heroDApicTr != null ? heroDApicTr : settleRoot, "MASK");
|
||||
Transform lihuiTr = FindDescendantByName(heroMaskTr != null ? heroMaskTr : settleRoot, "lihui");
|
||||
RectTransform teamDisplayingRt = FindRectByName(rightRoot != null ? rightRoot : settleRoot, "teamDisplaying");
|
||||
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<Transform> rightButtons = CollectDirectChildren(buttonsRoot);
|
||||
@@ -496,7 +535,25 @@ public class settlementController : MonoBehaviour
|
||||
if (rewardRt != null) { SetAnchoredY(rewardRt, -494.3f); SetCanvasAlpha(rewardRt, 0f); }
|
||||
|
||||
if (lihuiTr != null) SetCanvasAlpha(lihuiTr, 0f);
|
||||
if (teamDisplayingRt != null) { SetAnchoredX(teamDisplayingRt, 2265f); SetCanvasAlpha(teamDisplayingRt, 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++)
|
||||
{
|
||||
@@ -566,10 +623,20 @@ public class settlementController : MonoBehaviour
|
||||
yield return StartCoroutine(PlayStatusGroupIn(goodGroup, goodHitCount_Text, targetGoodCount, goodHitPercent_Text, targetGoodPercent));
|
||||
yield return StartCoroutine(PlayStatusGroupIn(missGroup, missHitCount_Text, targetMissCount, missHitPercent_Text, targetMissPercent));
|
||||
|
||||
// heroDApic/MASK/lihui flash + teamDisplaying move/fade in
|
||||
// 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));
|
||||
if (teamDisplayingRt != null)
|
||||
|
||||
// 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
|
||||
@@ -584,9 +651,125 @@ public class settlementController : MonoBehaviour
|
||||
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<Transform> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Documentation text normalized.
|
||||
/// </summary>
|
||||
@@ -823,6 +1006,32 @@ public class settlementController : MonoBehaviour
|
||||
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;
|
||||
@@ -833,6 +1042,7 @@ public class settlementController : MonoBehaviour
|
||||
|
||||
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));
|
||||
@@ -853,6 +1063,7 @@ public class settlementController : MonoBehaviour
|
||||
|
||||
while (elapsed < dur)
|
||||
{
|
||||
if (skipIntroRequested) break;
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
float t = EaseOutCubic(Mathf.Clamp01(elapsed / dur));
|
||||
float value = Mathf.Lerp(0f, toValue, t);
|
||||
@@ -874,6 +1085,7 @@ public class settlementController : MonoBehaviour
|
||||
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));
|
||||
@@ -896,6 +1108,7 @@ public class settlementController : MonoBehaviour
|
||||
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));
|
||||
@@ -920,6 +1133,7 @@ public class settlementController : MonoBehaviour
|
||||
float elapsed = 0f;
|
||||
while (elapsed < total)
|
||||
{
|
||||
if (skipIntroRequested) break;
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
float p = Mathf.Clamp01(elapsed / total);
|
||||
|
||||
@@ -990,12 +1204,13 @@ public class settlementController : MonoBehaviour
|
||||
return 1f - inv * inv * inv;
|
||||
}
|
||||
|
||||
private static IEnumerator WaitRealtime(float duration)
|
||||
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;
|
||||
}
|
||||
@@ -1347,4 +1562,3 @@ public class settlementController : MonoBehaviour
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user